基本使用

ES6新增的引用类型Promise,可以通过new操作符来实例化。创建时需要传入执行器函数作为参数。

  1. new Promise(function (resolve, reject) {
  2. // 实例化后立即执行,同步执行
  3. console.log("promise");
  4. });

Promise是一个异步操作,是一个有状态的对象,可能处于如下 3 种状态之一:

  • 待定(pending
  • 兑现(fulfilled,有时候也称为“解决”,resolved
  • 拒绝(rejected

待定是期约的最初始状态。在待定状态下,Promise可以落定为代表成功的兑现状态,或者代表失败的拒绝状态。无论落定为哪种状态后就无法再改变。

如果想要将Promise对象的状态切换为兑现或者拒绝的话需要调用执行器函数的relovereject方法。

  1. new Promise(function (resolve, reject) {
  2. // 切换为兑现状态
  3. resolve();
  4. });
  5. new Promise(function (resolve, reject) {
  6. // 切换为拒绝状态
  7. reject();
  8. });

无论切换兑现还是拒绝状态后,Promise的状态都不可以更改

  1. let p = new Promise((resolve, reject) => {
  2. resolve();
  3. reject(); // 没有效果
  4. });
  5. setTimeout(console.log, 0, p); // Promise <resolved>

resolvereject也可以传递参数,等待then方法去处理。

  1. let p1 = new Promise((resolve, reject) => {
  2. resolve(123);
  3. });
  4. // 两个期约实例实际上是一样
  5. let p2 = Promise.resolve(123);

注意,**resolve**方法能够包装任何非**Promise**值,包括错误对象,并将其转换为解决的期约。因此,也可能导致不符合预期的行为:

  1. let p = Promise.resolve(new Error('foo'));
  2. setTimeout(console.log, 0, p); // Promise <resolved>: Error: foo

resolve()类似,reject()会实例化一个拒绝的期约并抛出一个异步错误 (这个错误不能通过try/catch捕获,而只能通过拒绝处理程序捕获)。

  1. let p1 = new Promise((resolve, reject) => reject());
  2. // 两个期约实例实际上是一样的
  3. let p2 = Promise.reject();

then()、catch()、finally()

Promise的实例对象有三个方法用于处理resolvereject的结果。

then()方法接受一个或者两个参数,第一个参数表示resolve兑现状态的时候执行,第二个参数表示reject拒绝状态的时候执行。

  1. let p1 = new Promise((resolve, reject)=>{
  2. resolve("成功");
  3. });
  4. p1.then((res)=>{
  5. console.log(res); // 成功
  6. })
  7. let p2 = new Promise((resolve, reject)=>{
  8. reject("失败");
  9. });
  10. p2.then(
  11. (res)=>{
  12. console.log(res);
  13. },
  14. (error)=>{
  15. console.log(error); // 失败
  16. })

如果不想写兑现状态的处理函数,可以填为null

  1. let p2 = new Promise((resolve, reject)=>{
  2. reject("失败");
  3. });
  4. p2.then(
  5. null,
  6. (error)=>{
  7. console.log(error); // 失败
  8. })

catch()方法表示reject拒绝状态的时候执行,也就是then方法的第二个参数
事实上,这个方法就是一个语法糖,调用它就相当于调用Promise.prototype.then(null, onRejected)

  1. let p2 = new Promise((resolve, reject)=>{
  2. reject("失败");
  3. });
  4. p2.then((res)=>{
  5. console.log(res);
  6. }).catch(error=>{
  7. console.log(error); // 失败
  8. })

如果resolve时发送错误,会更改promise的状态为已失败,然后被catch的第二个方法捕获:

  1. let promise = new Promise((resolve, reject) => {
  2. resolve(a);
  3. });
  4. promise.catch((res) => {
  5. console.log(res); // a is not defined
  6. });

finally这个处理程序在期约转换为解决或拒绝状态时都会执行。这个方法可以避免onResolvedonRejected处理程序中出现冗余代码。

  1. let p1 = Promise.resolve();
  2. let p2 = Promise.reject();
  3. let onFinally = function() {
  4. setTimeout(console.log, 0, 'Finally!')
  5. }
  6. p1.finally(onFinally); // Finally
  7. p2.finally(onFinally); // Finally

状态依赖

Promise的状态依赖另一个Promise状态的时候,前者Promise的状态会失效,然后变更为后者Promise的状态。

  1. let p1 = new Promise((resolve, reject) => {
  2. setTimeout(() => {
  3. reject(new Error("fail"));
  4. }, 3000);
  5. });
  6. // 当p2依赖p1的时候p2的状态无效,而是变更为p1的状态
  7. let p2 = new Promise((resolve, reject) => {
  8. setTimeout(() => {
  9. resolve(p1);
  10. }, 1000);
  11. });
  12. p2.then().catch((err) => {
  13. console.log(err); // fail
  14. });

微任务与宏任务

JS中异步代码中分为「微任务」和「宏任务」,两种任务都有自己的任务队列;微任务包括PromiseNode中的process.nextTick(),宏任务包括除上面两种外其他的异步,例如setTimeout
每次事件轮询的时候,调用任务队列,这个时候存在优先级,优先执行微任务(微任务要比宏任务先执行)
JS异步代码执行的机制:
剖析 JavaScript 的执行机制

举例:

  1. setTimeout(() => {
  2. console.log("timeout");
  3. }, 30);
  4. let promise = new Promise((resolve, reject) => {
  5. console.log(0);
  6. resolve(1);
  7. });
  8. promise.then((res) => {
  9. console.log(res);
  10. });
  11. console.log(2);
  12. // 0 2 1 timeout
  13. // 解析:
  14. // 1、setTimeout执行,回调函数被挂起
  15. // 2、Promise执行函数内部代码是同步执行,所以打印 0,然后resolve会被挂起
  16. // 3、接着继续执行同步代码,打印 2
  17. // 4、因为微任务Promise要先比宏任务setTimeout执行,所以先执行 then 方法,打印1
  18. // 5、等then执行完成后,setTimeout的回调执行,打印 timeout
  1. Promise.resolve().then((res) => {
  2. console.log("promise1");
  3. setTimeout(() => {
  4. console.log("setTimeout2");
  5. });
  6. });
  7. setTimeout(() => {
  8. console.log("setTimeout1");
  9. Promise.resolve().then((res) => {
  10. console.log("promise2");
  11. });
  12. });
  13. // promise1 setTimeout1 promise2 setTimeout2
  14. // 解析:
  15. // 1、微任务Promise先执行,打印 promise1,然后setTimeout挂起
  16. // 2、setTimeout 执行,打印 setTimeout1 ,然后Promise挂起
  17. // 3、先执微任务,打印 promise2
  18. // 4、最后执行 setTimeout2

实现 Thenable 接口

ECMAScript暴露的异步结构中,任何对象都有一个then()方法。这个方法被认为实现了Thenable接口。
如果对象上有then方法,就可以直接调用resolve方法,此时p的状态由obj对象来决定。

  1. let obj = {
  2. then: function (resolve, reject) {
  3. resolve(123);
  4. },
  5. };
  6. let p = Promise.resolve(obj);
  7. // 第 9 行的 then 可以理解为第 3 行 resolve 的结果
  8. p.then((res) => {
  9. console.log(res); // 123
  10. });
  1. let obj = {
  2. testThen: function (resolve, reject) {
  3. resolve(123);
  4. },
  5. };
  6. let p = Promise.resolve(obj);
  7. p.then((res) => {
  8. console.log(res); // {testThen: ƒ}
  9. });

链式调用

then()方法返回一个新的期约实例,如果没有提供then处理程序,则Promise.resolve()就会包装上一个期约解决之后的值。
如果没有显式的返回语句,则Promise.resolve()会包装默认的返回值undefined

  1. let p1 = Promise.resolve('foo');
  2. // 若调用 then() 时不传处理程序,则原样向后传
  3. let p2 = p1.then();
  4. console.log(p2); // Promise {<fulfilled>: 'foo'}
  5. // 这些都一样
  6. let p3 = p1.then(() => undefined);
  7. let p4 = p1.then(() => {});
  8. let p5 = p1.then(() => Promise.resolve());
  9. let p6 = p1.then(() => 'bar');
  10. let p7 = p1.then(() => Promise.resolve('bar'));
  11. let p8 = p1.then(() => Promise.reject());
  12. setTimeout(console.log, 0, p3); // Promise {<fulfilled>: 'undefined'}
  13. setTimeout(console.log, 0, p4); // Promise {<fulfilled>: 'undefined'}
  14. setTimeout(console.log, 0, p5); // Promise {<fulfilled>: 'undefined'}
  15. setTimeout(console.log, 0, p6); // Promise {<fulfilled>: 'bar'}
  16. setTimeout(console.log, 0, p7); // Promise {<fulfilled>: 'bar'}
  17. setTimeout(console.log, 0, p8); // Promise <rejected>: undefined

必须使用return才能拿到值:

  1. let promise = new Promise((resolve, reject) => {
  2. resolve("a");
  3. });
  4. promise
  5. .then((res) => {
  6. console.log(res); // a
  7. return res + "b";
  8. })
  9. .then((res) => {
  10. console.log(res); // ab
  11. return res + "c";
  12. })
  13. .then((res) => {
  14. console.log(res); // abc
  15. });

每个期约实例的方法(then()catch()finally())都会返回一个新的期约对象,而这个新期约又有自己的实例方法。这样连缀方法调用就可以构成链式调用。

  1. let p1 = new Promise((resolve, reject) => {
  2. console.log('p1 executor');
  3. setTimeout(resolve, 1000);
  4. });
  5. p1.then(() => new Promise((resolve, reject) => {
  6. console.log('p2 executor');
  7. setTimeout(resolve, 1000);
  8. }))
  9. .then(() => new Promise((resolve, reject) => {
  10. console.log('p3 executor');
  11. setTimeout(resolve, 1000);
  12. }))
  13. .then(() => new Promise((resolve, reject) => {
  14. console.log('p4 executor');
  15. setTimeout(resolve, 1000);
  16. }));
  17. // p1 executor(1 秒后)
  18. // p2 executor(2 秒后)
  19. // p3 executor(3 秒后)
  20. // p4 executor(4 秒后)

如何把生成期约的代码提取到一个工厂函数中,就可以写成这样:

  1. function delayedResolve(str) {
  2. return new Promise((resolve, reject) => {
  3. console.log(str);
  4. setTimeout(resolve, 1000);
  5. });
  6. }
  7. delayedResolve('p1 executor')
  8. .then(() => delayedResolve('p2 executor'))
  9. .then(() => delayedResolve('p3 executor'))
  10. .then(() => delayedResolve('p4 executor'))
  11. // p1 executor(1 秒后)
  12. // p2 executor(2 秒后)
  13. // p3 executor(3 秒后)
  14. // p4 executor(4 秒后)

all() 和 race()

Promise.all()静态方法创建的Promise会在一组Promise全部解决之后再解决。这个静态方法接收一个可迭代对象,返回一个新期约。

  1. let p1 = Promise.all([
  2. Promise.resolve(),
  3. Promise.resolve()
  4. ]);
  5. // 可迭代对象中的元素会通过 Promise.resolve() 转换为期约
  6. let p2 = Promise.all([3, 4]);
  7. // 空的可迭代对象等价于 Promise.resolve()
  8. let p3 = Promise.all([]);
  9. // 无效的语法
  10. let p4 = Promise.all(); // TypeError: cannot read Symbol.iterator of undefined

如果所有Promise都成功解决,则then方法的参数就是所有解决的数组,按照迭代器顺序:

  1. let p = Promise.all([
  2. Promise.resolve(3),
  3. Promise.resolve(),
  4. Promise.resolve(4)
  5. ]);
  6. p.then((values) => setTimeout(console.log, 0, values)); // [3, undefined, 4]

如果有一个包含失败状态的Promise,则合成的Promise也会失败,第一个失败的Promise会将自己的理由作为合成Promise的拒绝理由:

  1. // 一次拒绝会导致最终期约拒绝
  2. let p2 = Promise.all([
  3. Promise.resolve(),
  4. Promise.reject("失败"),
  5. Promise.resolve()
  6. ]);
  7. setTimeout(console.log, 0, p2); // Promise {<rejected>: '失败'}
  8. p2.catch((error)=>{
  9. console.log(error); // 失败
  10. })

Promise.race()all()基本类似,也返回一个包装Promise,不同的是race不管任意一个Promise先完成状态的落定就会处理。

  1. let p1 = Promise.race([
  2. Promise.resolve(),
  3. Promise.resolve()
  4. ]);
  5. // 可迭代对象中的元素会通过 Promise.resolve() 转换为期约
  6. let p2 = Promise.race([3, 4]);
  7. // 空的可迭代对象等价于 new Promise(() => {})
  8. let p3 = Promise.race([]);
  9. // 无效的语法
  10. let p4 = Promise.race(); // TypeError: cannot read Symbol.iterator of undefined

无论是解决还是拒绝,只要是第一个落定的PromisePromise.race()就会包装其解决值或拒绝理由并返回新Promise

  1. // 解决先发生,超时后的拒绝被忽略
  2. let p1 = Promise.race([
  3. Promise.resolve(3),
  4. new Promise((resolve, reject) => setTimeout(reject, 1000))
  5. ]);
  6. setTimeout(console.log, 0, p1); // Promise <resolved>: 3
  7. // 拒绝先发生,超时后的解决被忽略
  8. let p2 = Promise.race([
  9. Promise.reject(4),
  10. new Promise((resolve, reject) => setTimeout(resolve, 1000))
  11. ]);
  12. setTimeout(console.log, 0, p2); // Promise <rejected>: 4
  13. // 迭代顺序决定了落定顺序
  14. let p3 = Promise.race([
  15. Promise.resolve(5),
  16. Promise.resolve(6),
  17. Promise.resolve(7)
  18. ]);
  19. setTimeout(console.log, 0, p3); // Promise <resolved>: 5