Promise

文章目录

MDN

总结:

  • Promise 是同步代码,Promise.then 是 微任务

  • then 的执行时机是在前面函数执行完成并且 Promise 状态变更以后才会被添加到微任务队列中等待执行

  • then 的 onFulfilled 不是函数,则无效

  • then 的 onFulfilled 是函数,则除了 throw error 其他返回值都会转为 Promise 对象

  • thenable 是微任务

  • thenable 中如果没有执行 resolve(),则 thenable 后面如果还有 then,则不执行

  • await 表达式的 Promise 状态不变更,await 下的代码以及之后的then都不会执行

Promise【ES2015】

promise 为 es6 语法,
Promise 对象是一个构造函数,用来生成Promise实例。
Promise对象,是代表了未来某个将要发生的事件(通常是一个异步操作)

注意:promise里面的内容会直接执行,可在外层使用函数包裹

优点:

  • 将异步操作以同步操作的流程表达出来
  • 避免了层层嵌套的回调函数(回调地狱),让异步维护更加方便
  • 代码可读性更高

最简单写法

function fn() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("异步 11");
      resolve();
    }, 1000);
  });
}
fn();
console.log("22");

实例:请求图片

const loadImg = new Promise((resolve, reject) => {
  const img = new Image();
  // img.src = "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fa3.att.hudong.com%2F61%2F98%2F01300000248068123885985729957.jpg&refer=http%3A%2F%2Fa3.att.hudong.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1621179828&t=4b1d14126d405dbd3a25a7caca3dcde2";
  img.src = "https://gimg2.baidu.com/im" // 模拟加载失败
  
  img.onload = (res) => {
    // console.log(res);
    resolve("图片加载成功");
  };

  img.onerror = () => {
    reject("图片加载失败");
  };
});

loadImg.then(
  (res) => {
    console.log(res);
    console.log('成功之后的逻辑')
  },
  (err) => {
    console.log(err);
  }
);

promise 三个状态

pending 正在进行中

let p = new Promise(() => {});
console.log(p);

打印结果如下:
在这里插入图片描述

fulfilled 已完成

let p = new Promise((resolve, reject) => {
	resolve();
});
console.log(p);

打印 resolve() 后的状态
在这里插入图片描述

rejected 已拒绝

let p = new Promise((resolve, reject) => {
	reject();
});
console.log(p);

在控制台打印 test,打印 reject() 后的状态
在这里插入图片描述

promise 对象原型方法

Promise.prototype.then

MDN

then(onFulfilled, onRejected)

let p = new Promise((resolve, reject) => {
    setTimeout(() => {
    	console.log("异步 11");
    	resolve('success');
    	// reject('error');
    }, 1000);
});

p.then(res => {
	console.log('完成', res)
}, err => {
	console.log('错误', err)
})

onFulfilled

onFulfilled:成功回调 (res) => {}

当 Promise 变成接受状态(fulfilled)时调用的函数。该函数有一个参数,即接受的最终结果(the fulfillment value)。

onFulfilled 不是函数:该then无效

参考 MDN

then 里接收的是非函数则会将 promise 对象传递到下一个 then 里

then(1):

new Promise((resolve) => {
  resolve(0)
}).then(1).then(res => {
  console.log(res) // 0
})

如果该参数不是函数,则会在内部被替换为 (x) => x,即原样返回 promise 最终结果的函数 ——来自MDN

理解为 then(1) 会被替换为 then((x) => x),x 也就是 resolve(0) 的值 0。所以 res 得到的是 0

then(thenable):

new Promise((resolve) => {
  resolve(0)
}).then({
  then(resolve) {
    console.log(1)
    resolve(2)
  }
}).then(res => {
  console.log(res) // 0
})

只要onFulfilled不是函数,结果都一样

then(Promise):

new Promise((resolve) => {
  resolve(0)
}).then(Promise.resolve(1)).then(res => {
  console.log(res) // 0
})

只要onFulfilled不是函数,结果都一样

onFulfilled 是函数:then(() => {})
没有返回任何值

没有返回任何值,那么 then 返回的 Promise 将会成为接受状态,并且该接受状态的回调函数的参数值为 undefined

new Promise((resolve) => {
  console.log(1)
  resolve()
}).then(() => {
  console.log(2)
  // 无 return,等同于: return Promise.resolve()
}).then(res => {
  console.log(res) // undefined
})

打印结果:

1
2
undefined
有返回值

这里值返回不为Promise/thenable 的值

返回了一个值,那么 then 返回的 Promise 将会成为接受状态,并且将返回的值作为接受状态的回调函数的参数值。

new Promise((resolve) => {
  console.log(1)
  resolve()
}).then(() => {
  console.log(2)
  return 100 // 等同于: return Promise.resolve(100)
}).then(res => {
  console.log(res) // 100
})

打印结果:

1
2
100

扩展-值是对象:

new Promise((resolve) => {
  console.log(1)
  resolve()
}).then(() => {
  console.log(2)
  return { name: '123' } // 等同于: return Promise.resolve({ name: '123' })
}).then(res => {
  console.log(res) // {name: '123'}
})

打印结果

1
2
{name: '123'}
返回 thenable
thenable 执行resolve()

返回值返回的是一个对象,且不是promise对象,并且该对象里面有个then方法

new Promise((resolve) => {
  console.log(1)
  resolve()
}).then(() => {
  console.log(2)
  return {
    then (resolve, reject) {
      console.log(3);
      resolve(4)
    }
  }
}).then(res => {
  console.log(res)
})

打印结果

1
2
3
4
thenable 不执行resolve()

如果 thenable 里面没有执行 resolve(),那么这个promise后面的.then方法将不执行

new Promise((resolve) => {
  console.log(1)
  resolve()
}).then(() => {
  console.log(2)
  return {
    then () {
      console.log(3);
    }
  }
}).then(res => { // 注意:此then不执行
  console.log(res)
})

打印结果

1
2
3
返回 Promise对象
返回一个未定状态(pending)的 Promise

返回一个未定状态(pending)的 Promise,那么 then 返回 Promise 的状态也是未定的

new Promise((resolve) => {
  console.log(1)
  resolve()
}).then(() => {
  console.log(2)
  return new Promise(() => {})
}).then(res => {
  console.log(res)
})

打印结果

1
2
返回一个已经是接受状态的 Promise

返回一个已经是接受状态的 Promise,那么 then 返回的 Promise 也会成为接受状态

并且将那个 Promise 的接受状态的回调函数的参数值作为该被返回的Promise的接受状态回调函数的参数值。

new Promise((resolve) => {
  console.log(1)
  resolve()
}).then(() => {
  console.log(2)
  return new Promise((resolve, reject) => {
    resolve('success')
  })
}).then(res => {
  console.log(res) // success
})

打印结果

1
2
success
返回一个已经是拒绝状态的 Promise

返回一个已经是拒绝状态的 Promise,那么 then 返回的 Promise 也会成为拒绝状态,并且将那个 Promise 的拒绝状态的回调函数的参数值作为该被返回的Promise的拒绝状态回调函数的参数值。

new Promise((resolve) => {
  console.log(1)
  resolve()
}).then(() => {
  console.log(2)
  return new Promise((resolve, reject) => {
    reject('error')
  })
}).then(res => {
  console.log(res) // Uncaught (in promise) error
})

打印结果

1
2
Uncaught (in promise) error
抛出一个错误

抛出一个错误,那么 then 返回的 Promise 将会成为拒绝状态,并且将抛出的错误作为拒绝状态的回调函数的参数值。

new Promise((resolve) => {
  console.log(1)
  resolve()
}).then(() => {
  console.log(2)
  throw new Error('出错了')
}).then(res => {
  console.log(res)
}, err => {
  console.log(err); // Error: 出错了
})

打印结果

1
2
Error: 出错了

onRejected

onRejected:失败回调 (err) => {}

当 Promise 变成拒绝状态(rejected)时调用的函数。该函数有一个参数,即拒绝的原因(rejection reason)。如果该参数不是函数,则会在内部被替换为一个 “Thrower” 函数 (it throws an error it received as argument)。

onRejected 与 catch 优先级
new Promise((resolve, reject) => {
  reject(new Error('11'))
}).then(res => {
  console.log('then res:', res)
}, err => {
  console.log('then err:', err)
}).catch(err => {
  console.log('catch err:', err)
}).finally(() => {
  console.log('finally', res)
})

执行结果:
在这里插入图片描述

结论:

  • then 的错误回调和catch同时存在,then的错误回调 优先级大于 catch。

  • 当不存在then的错误回调时,才会走catch
    在这里插入图片描述

链式操作

let p = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log("异步 11");
    resolve("success");
  }, 1000);
});

const p2 = p.then((res) => {
  console.log("异步完成", res);
}).then((res) => {
  console.log("异步完成1", res);
}).then((res) => {
  console.log("异步完成2", res);
});

console.log(p2);

在这里插入图片描述

p.then().then()p.then() p.then() 的区别

promise内都是同步代码看不出区别来,如果是异步代码就可以看出区别

then 中同步代码

首先看下 p.then() p.then()

let p = new Promise((resolve) => {
  console.log(1)
  resolve()
})

p.then(() => {
  console.log(2)
})
p.then(res => {
  console.log(3)
})

打印结果:

1
2
3

再看下 p.then().then()

let p = new Promise((resolve) => {
  console.log(1)
  resolve()
})

p.then(() => {
  console.log(2)
}).then(res => {
  console.log(3)
})

打印结果:

1
2
3

上面都是同步代码,从打印结果上看不出任何区别,下面在then中使用异步代码进行测试

then 中异步代码

还是先看 p.then() p.then()

let p = new Promise((resolve) => {
  console.log(1)
  resolve()
})

p.then(() => {
  console.log(2)
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('2-1');
      resolve()
    }, 1000);
  })
})
p.then(res => {
  console.log(3)
})

打印结果

1
2
3
2-1 // 过一秒出来

再看 p.then().then()

let p = new Promise((resolve) => {
  console.log(1)
  resolve()
})

p.then(() => {
  console.log(2)
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('2-1');
      resolve()
    }, 1000);
  })
}).then(res => {
  console.log(3)
})

打印结果

1
2
2-1 // 过一秒出来
3
结论

then中是同步代码那么 p.then().then() 链式调用跟 p.then() p.then() 执行结果一致。

then中是异步代码,两者的执行结果不一致,所以两者看似没有区别,实际上是有很大区别的,这是为什么?

分析:

p = new promise() 可以看成发布订阅(发布任务),resolve 相当于 notify 任务结束通知

then 可以看成监听 addEventListener

p.then() p.then() 可以看成是对这个 p 这个对象添加了2个观察者,一旦监听被触发,所有的观察者都会同时执行

p.then().then() 相当于一个函数调完后有返回值,另一个函数接着上一个函数执行

补充:p.then().then() 可以等同于如下
let p = new Promise((resolve) => {
  console.log(1)
  resolve()
})

p.then(() => {
  console.log(2)
}).then(res => {
  console.log(3)
})

// p.then().then() 等同于如下
let p2 = p.then(() => {
  console.log(2)
})

p2.then(res => {
  console.log(3)
})

Promise.prototype.catch

MDN:catch

let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log("异步 11");
        resolve("success");
    }, 1000);
});

const p2 = p.then((res) => {
    console.log("异步完成", res);
}).catch(err => {
    console.log(err)
}).then(res => {
    console.log(res)
});

console.log(p2);

在这里插入图片描述

参数:参照 then

由于catch返回值与then一样,返回值为 promise对象,所以catch 之后还可以then

Promise.prototype.finally 【ES2018】

MDN:finally

finally() 方法返回一个Promise。在promise结束时,无论结果是 fulfilled 或者是 rejected,都会执行指定的回调函数。这为在Promise是否成功完成后都需要执行的代码提供了一种方式。

这避免了同样的语句需要在then()catch()中各写一次的情况。

let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log("异步 11");
        // resolve("success");
        reject('error')
    }, 1000);
});

p.then(res=>{
    console.log(res);
},err=>{
    console.log(err);
}).finally(()=>{
    console.log("执行完成");  
})

success情况:在这里插入图片描述

error情况:在这里插入图片描述

Promise 静态方法

Promise.resolve()

MDN

返还一个 状态为 resolved 的 promise对象

const p = Promise.resolve(() => {
    setTimeout(() => {
        console.log('111')
    }, 1000)
})
console.log(p)

在这里插入图片描述

Promise.reject()

MDN

返还一个 状态为 rejected 的 promise对象

const p = Promise.reject(() => {
    setTimeout(() => {
        console.log('111')
    }, 1000)
})
console.log(p)

在这里插入图片描述

Promise.all()

MDN

同步执行所有的Promise

  • 所有的 promise 都成功才成功,返回 存放所有 resolve值的数组

  • 有一个失败即算失败,返回第一个错误结果,其他Promise会执行,但是执行结果不返回

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log("111");
    resolve(1);
  }, 1500);
})
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log("222");
    resolve(2);
  }, 2000);
})
const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log("333");
    resolve(3);
  }, 1000);
})
Promise.all([p1, p2, p3]).then((res) => {
  console.log('res: ', res)
})

注意:不管 Promise.all 执不执行,p1 p2 p3 里面的异步都会执行
在这里插入图片描述

  • 存在reject

    const p1 = new Promise((resolve, reject) => {
      setTimeout(() => {
        console.log("111");
        reject(1);
      }, 1500);
    })
    const p2 = new Promise((resolve, reject) => {
      setTimeout(() => {
        console.log("222");
        reject(2);
      }, 2000);
    })
    const p3 = new Promise((resolve, reject) => {
      setTimeout(() => {
        console.log("333");
        resolve(3);
      }, 1000);
    })
    Promise.all([p1, p2, p3]).then((res) => {
      console.log('res: ', res)
    }, err => {
      console.log('err:', err)
    })
    

在这里插入图片描述

Promise.allSettled() 【ES2020】

MDN

同步执行所有的Promise,不管是成功还是失败都会返回 {status, reason | value } 的对象数组

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log("111");
    reject(1);
  }, 1500);
})
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log("222");
    resolve(2);
  }, 2000);
})
const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log("333");
    resolve(3);
  }, 1000);
})
Promise.allSettled([p1, p2, p3]).then((res) => {
  console.log('res: ', res)
})

在这里插入图片描述

Promise.race()

MDN

先执行完成的先返回,无论成功还是失败只会返回第一个执行完毕的结果

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log("111");
    resolve(1);
  }, 1500);
})
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log("222");
    resolve(2);
  }, 2000);
})
const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log("333");
    resolve(3);
  }, 1000);
})
Promise.race([p1, p2, p3]).then((res) => {
  console.log('res: ', res)
}, err => {
  console.log('err:', err)
})

在这里插入图片描述

假设最快执行完毕的是reject,则返回reject
在这里插入图片描述

Promise.any() 【ES2021】

MDN

同步执行所有的Promise

  • 所有失败才算失败,返回 存放所有 error 的数组
  • 有一个成功就算成功,返回最快resolve的结果

所有都是reject:

const promises = [
  Promise.reject('ERROR A'),
  Promise.reject('ERROR B'),
  Promise.reject('ERROR C'),
]

Promise.any(promises).then((value) => {
  console.log('value:', value)
}).catch((err) => {
  console.log('err:', err)
  console.log(err.message)
  console.log(err.name)
  console.log(err.errors)
})

// err:AggregateError: All promises were rejected
// All promises were rejected
// AggregateError
// ["ERROR A", "ERROR B", "ERROR C"]

有一个resolve:

const promises = [
  Promise.reject('ERROR A'),
  Promise.reject('ERROR B'),
  Promise.resolve('result'),
]

Promise.any(promises).then((value) => {
  console.log('value: ', value)
}).catch((err) => {
  console.log('err: ', err)
})

// value:  result

Promise 问题

永不 resolve/reject 的 Promise 会导致内存泄漏吗?

参考:
https://zhuanlan.zhihu.com/p/385764204?ivk_sa=1024320u
https://www.shuzhiduo.com/A/xl56Ae94Jr/
https://www.zhihu.com/question/386595851

跟内存泄漏没有直接关系
gc 的策略不会改变,如果该 promise 没有被人引用,就会被 gc 掉。如果仍被引用,就不会被 gc 掉。
即使一个 promise,resolve 或者 reject 了,但是它还被人引用,仍然占用内存。

结论:

  1. 未执行完成的 Promise(包括内部等待的回调未完成),并被引用着,会占用内存
  2. 执行完成的 Promise(包括内部等待的回调也执行完成),不占用内存,可被 GC 释放
  3. 如果多次使用同一个 new Promise ,且没有 resolve / reject不会被垃圾回收
  4. 如果每次都生成新的 new Promise ,且没有 resolve / reject直接被GC 释放

GC:垃圾收集

如果使用同一个 new Promise ,且没有 resolve / reject,不会被垃圾回收

const pendingPromise = new Promise(() => {})

const dummyGet1 = () => {
  return pendingPromise
}

const next = (res) => {
  console.log(res)
}

// pending 1000, 一直存在,没被回收
// 需要使用.then,不使用then会被垃圾回收
Array(1000)
  .fill(1)
  .forEach(() => dummyGet1().then(next))

作者:蔡锦
链接:https://www.zhihu.com/question/386595851/answer/1270721798
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

在这里插入图片描述

如果每次都生成新的 new Promise 且没有 resolve / reject,直接被垃圾回收

// 如果 resolve 没有被调用,那么就直接被回收。
const dummyGet2 = () => {
  return new Promise(() => {})
}

const next = (res) => {
  console.log(res)
}

// 被回收了
Array(1000)
  .fill(1)
  .forEach(() => dummyGet2().then(next))

作者:蔡锦
链接:https://www.zhihu.com/question/386595851/answer/1270721798
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
在这里插入图片描述

练一练

考验 promise.then(2) 的结果

// 给出一个 promise
var promise = new Promise(function (resolve, reject) {
  setTimeout(function () {
    resolve(0)
  }, 3000)
})

// 请问以下几种有何不同?
// 1
promise
  .then(() => {
    return Promise.resolve(1)
  })
  .then((n) => {
    console.log(n)
  })

// 2
promise
  .then(() => {
    return 2
  })
  .then((n) => {
    console.log(n)
  })

// 3
promise.then(3).then((n) => {
  console.log(n)
})

// 4
promise
  .then(() => {
    return 4
  })
  .then(5)
  .then((n) => {
    console.log(n)
  })

答案:2 0 4 1

1 => 1

2 => 2

3 => 0

4 => 4

说出下列代码执行结果?

let a

const b = new Promise((resolve, reject) => {
  console.log('promise1')
  resolve()
})
  .then(() => {
    console.log('promise2')
  })
  .then(() => {
    console.log('promise3')
  })
  .then(() => {
    console.log('promise4')
  })

a = new Promise(async (resolve, reject) => {
  console.log(a)
  await b
  console.log(a)
  console.log('after1')
  await a
  resolve(true)
  console.log('after2')
})

console.log('end')

答案:

promise1
undefined
end
promise2
promise3
promise4
Promise { [[PromiseState]]: "pending", [[PromiseResult]]: undefined }
after1

解析: 可以将 then 改写成 async/await 来看 ,如下代码:

a = new Promise((resolve, reject) => {
  console.log(a) //undefined
  return Promise.resolve(b).then((res) => {
    console.log(a)
    console.log('after1')
    return Promise.resolve(a).then((res) => {
      console.log('after2')
      resolve(true)
    })
  })
})

然后先拿出同步执行的代码 ,同步执行代码有: console.log('promise1'); console.log(a); console.log('end'); 由于在打印的时候a还没有被赋值,所以得到打印undefined 。 故先得到打印 promise1 undefined end

接下来在来看异步代码执行 ,先会执行 Promise.resolve(b) ,得到打印console.log('promise2'); console.log('promise3'); console.log('promise4');

之后执行返还 promisethen 里的内容 由于是异步,故 a 已经完成了赋值操作,但是还未执行到resolve调用,所以只能得到 pending状态的promise对象。得到打印 Promise {} 然后打印 after1

由于 Promise.resolve(a)then执行需要再 a promise 调取 resolve 之后执行,此时 apromise 并没有调用 resolve 所以得不到 then 里的打印 。

综上所述:打印结果是: promise1--> undefined --> end-->promise2---> promise3--->promise4--->Promise {} --->after1

请说出以下执行结果?

function test() {
  console.log(1)
  setTimeout(function () {
    // timer1
    console.log(2)
  }, 1000)
}

test()

setTimeout(function () {
  // timer2
  console.log(3)
})

new Promise(function (resolve) {
  console.log(4)

  setTimeout(function () {
    // timer3
    console.log(5)
  }, 100)

  resolve() // 考察点在这个
}).then(function () {
  setTimeout(function () {
    // timer4
    console.log(6)
  }, 0)
  console.log(7)
})

console.log(8)

执行结果:

1
4
8
7
3
6
5
2

按要求完成 mergePromise 代码

const timeout = (ms) =>
  new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve()
    }, ms)
  })

const ajax1 = () =>
  timeout(2000).then(() => {
    console.log('1')
    return 1
  })

const ajax2 = () =>
  timeout(1000).then(() => {
    console.log('2')
    return 2
  })

const ajax3 = () =>
  timeout(2000).then(() => {
    console.log('3')
    return 3
  })

const mergePromise = (ajaxArray) => {
  // 1,2,3 done [1,2,3]
}

mergePromise([ajax1, ajax2, ajax3]).then((data) => {
  console.log('done')
  console.log(data) // data 为[1,2,3]
})

// 要求执行结果为:1 2 3 done [1,2,3]

答案 :

  • 递归
const mergePromise = (ajaxArray) => {
  // 1,2,3 done [1,2,3]
  return new Promise((resolve, reject) => {
    let num = 0
    let resArr = []

    fn()
    function fn() {
      if (num === ajaxArray.length) {
        resolve(resArr)
        return
      }

      ajaxArray[num]().then((res) => {
        num++
        resArr.push(res)
        fn()
      })
    }
  })
}
  • 利用 async 及 await 来实现
const mergePromise = (ajaxArray) => {
  // 1,2,3 done [1,2,3]
  return new Promise(async (resolve, reject) => {
    let resArr = []

    for (let i = 0; i < ajaxArray.length; i++) {
      let res = await ajaxArray[i]()
      resArr.push(res)
    }
    resolve(resArr)
  })
}

请写出以下输出的顺序,并指出 js 中微任务和宏任务有哪些。

console.log('start')

setTimeout(() => {
  console.log('setTimeout')
}, 0)

new Promise((resolve) => {
  console.log('promise')
  resolve()
})
  .then(() => {
    console.log('then1')
  })
  .then(() => {
    console.log('then2')
  })

console.log('end')

答案:

  • 输出顺序:先执行同步,后执行微任务,最后再执行宏任务

    start
    promise
    end
    then1
    then2
    setTimeout
    
  • 宏任务

    setTimeout(() => {
      console.log('setTimeout')
    }, 0)
    
  • 微任务

    .then(() => {
      console.log('then1')
    }).then(() => {
      console.log('then2')
    })
    

尝试手写一个 Promise

涉及相关面试题:

  • 什么是微任务和宏任务

  • Promise如何实现?

    • 至少要把 3 种状态、Promise.then 写出来
  • Promise.allPromise.allSetlled … 原理写出来

  • handle(this.#resolve.bind(this), this.#reject.bind(this)) 为什么需要重新设置 this 指向

  • 9
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值