彻底弄懂Promise

262 阅读13分钟

为什么需要Promise

Javascript 是一⻔单线程语言,所以早期我们解决异步的场景时,大部分情况都是通过回调函数来进 行。

例如在浏览器中发送 ajax 请求,就是常⻅的一个异步场景,发送请求后,一段时间服务端响应之后我们 才能拿到结果。如果我们希望在异步结束之后执行某个操作,就只能通过回调函数这样的方式进行操作。

 var dynamicFunc = function(cb) {
  setTimeout(function() {
    cb();
  }, 1000);
}
dynamicFunc(function() {console.log(123)});

例如上面这个例子,这里的 dynamicFunc 就是一个异步的函数,里面执行的 setTimeout 会在 1s 之后调 用传入的 cb 函数。按照上面的调用方式,最终 1s 之后,会打印 123 这个结果。

同样的,如果后续还有内容需要在异步函数结束时输出的话,就需要多个异步函数进行嵌套,非常不利 于后续的维护。

 setTimeout(function() {
  console.log(123);
  setTimeout(function() {
      console.log(321);
  	// ...
  }, 2000);
}, 1000);

为了能使回调函数以更优雅的方式进行调用,在 ES6 中 js 产生了一个名为 promise 的新规范,他让异步 操作的变得近乎「同步化」。

Promise基础

在支持 ES6 的高级浏览器环境中,我们通过 new Promise() 即可构造一个 promise 实例。

这个构造函数接受一个函数,分别接受两个参数,resolve 和 reject,代表着我们需要改变当前实例的状 态到 已完成 或是 已拒绝 。

 function promise1() {
  return new Promise(function(resolve, reject) {
      // 定义异步的内容 
      setTimeout(function() {
        console.log('1s 后输出');
        // 输出完成后,调用函数传入的 resolve 函数,将该 promise 实例标记为已完成,当前 promise 串 行继续执行
        resolve();
      }, 1000);
}); }
 function promise3() {
  return new Promise(function(resolve, reject) {
  var random = Math.random() * 10; // 随机一个 1 - 10 的数字 
  setTimeout(function() {
        if (random >= 5) {
          resolve(random);
        } else {
          reject(random);
  }
  }, 1000);
}); }
var onResolve = function(val) { console.log('已完成:输出的数字是', val);
};
var onReject = function(val) { console.log('已拒绝:输出的数字是', val);
}
 // promise 的 then 也可以接受两个函数,第一个参数为 resolve 后执行,第二个函数为 reject 后执行 
promise3().then(onResolve, onReject);
// 也可以通过 .catch 方法拦截状态变为已拒绝时的 promise 
promise3().catch(onReject).then(onResolve);
// 也可以通过 try catch 进行拦截状态变为已拒绝的 promise 
try {
  promise3().then(onResolve);
} catch (e) {
  onReject(e);
}

这个例子使用了三种方式拦截最终变为「已拒绝」状态的 promise,分别是使用 then 的第二个参数,使 用 .catch 方法捕获前方 promise 抛出的异常,使用 try catch 拦截代码块中 promise 抛出的异常 同时我们还可以发现,在改变 promise 状态时调用 resolve 和 reject 函数的时候,也可以给下一步 then 中执行的函数传递参数。这个例子中我们把随机生成的数字传给了 resolve 和 reject 函数,我们也就能在 then 中执行函数的时候拿到这个值。

  1. promise 会有三种状态,「进行中」「已完成」和「已拒绝」,进行中状态可以更改为已完成或 已拒绝,已经更改过状态后无法继续更改(例如从已完成改为已拒绝)。
  2. ES6 中的 Promise 构造函数,我们构造之后需要传入一个函数,他接受两个函数参数,执行第一 个参数之后就会改变当前 promise 为「已完成」状态,执行第二个参数之后就会变为「已拒绝」 状态。
  3. 通过 .then 方法,即可在上一个 promise 达到已完成时继续执行下一个函数或 promise。同时通过 resolve 或 reject 时传入参数,即可给下一个函数或 promise 传入初始值。
  4. 已拒绝的 promise,后续可以通过 .catch 方法或是 .then 方法的第二个参数或是 try catch 进行捕 获。

如何封装异步操作为 promise

们可以将任何接受回调的函数封装为一个 promise,下面举几个简单的例子来说明。

 // 原函数
function dynamicFunc(cb) {
	setTimeout(function() { 
    	console.log('1s 后显示'); cb();
	}, 1000); 
}
var callback = function() { 
	console.log('在异步结束后 log');
}
// 用传入回调函数的方式执行 
dynamicFunc(callback);

上面的例子就是最传统的,使用传入回调函数的方式在异步结束后执行函数。我们可以通过封装 promise 的方式,将这个异步函数变为 promise。

function dynamicFuncAsync() {
  return new Promise(function(resolve) {
    setTimeout(function() { 
      console.log('1s 后显示'); resolve();
    }); 
  });
}
var callback = function() { 
  console.log('在异步结束后 log');
}
dynamicFuncAsync().then(function() { callback(); });

再举一个例子,发送 ajax 请求也可以进行封装:

 function ajax(url, success, fail) {
  var client = new XMLHttpRequest();
  client.open("GET", url);
  client.onreadystatechange = function() {
    if (this.readyState !== 4) {
      return;
    }
    if (this.status === 200) {
      success(this.response);
    } else {
      fail(new Error(this.statusText));
    }
};
  client.send();
};
ajax('/ajax.json', function() {console.log('成功')}, function() {console.log('失败')});

我们可以看到,调用 ajax 方法时需要传入 success 和 fail 的回调函数进行调用。我们可以不传入回调函 数,通过封装 promise 的方式,在原来的执行回调函数的地方更改当前 promise 的状态,就可以通过链 式调用。

 function ajaxAsync(url) {
  return new Promise(function(resolve, reject){
    var client = new XMLHttpRequest();
    client.open("GET", url);
    client.onreadystatechange = function() {
      if (this.readyState !== 4) {
        return;
      }
      if (this.status === 200) {
      	resolve(this.response);
      } else {
        reject(new Error(this.statusText));
      } 
    };
    client.send();
  });
};
ajaxAsync('/ajax.json')
  .catch(function() {
  	console.log('失败'); 
  })
  .then(function() { 
  	console.log('成功');
  })
  1. 我们可以轻松的把任何一个函数或者是异步函数改为 promise,尤其是异步函数,改为 promise 之 后即可进行链式调用,增强可读性。
  2. 将带有回调函数的异步改为 promise 也很简单,只需要在内部实例化 promise 之后,在原来执行 回调函数的地方执行对应的更改 promise 状态的函数即可。

promise 规范解读

任何符合 promise 规范的对象或函数都可以成为 promise,promise A plus 规范地址: promisesaplus.com/

上面我们熟悉了整体 promise 的用法,我们知道了如何去创建一个 promise,如何去使用它,后面我们 也熟悉了如何去改造回调函数到 promise。本小节我们详细过一遍 promise A+ 规范,从规范层面明白 promise 使用过程中的细节。

promise 的状态

一个 Promise 的当前状态必须为以下三种状态中的一种:等待态(Pending)、已完成(Fulfilled)和已 拒绝(Rejected) 。

处于等待态时,promise 需满足以下条件:可以变为「已完成」或「已拒绝」

处于已完成时,promise 需满足以下条件:1. 不能迁移至其他任何状态 2. 必须拥有一个不可变的值

处于已拒绝时,promise 需满足以下条件:1. 不能迁移至其他任何状态 2. 必须拥有一个不可变的原因

必须有一个 then 方法

一个 promise 必须提供一个 then 方法以访问其当前值和原因。

promise 的 then 方法接受两个参数: promise.then(onFulfilled, onRejected) 他们都是可选参数,同时他 们都是函数,如果 onFulfilled 或 onRejected 不是函数,则需要忽略他们。

如果 onFulfilled 是一个函数

当 promise 执行结束后其必须被调用,其第一个参数为 promise 的结果 在 promise 执行结束前其不可被调用其调用次数不可超过一次

如果 onRejected 是一个函数

当 promise 被拒绝执行后其必须被调用,其第一个参数为 promise 的原因 在 promise 被拒绝执行前其不可被调用其调用次数不可超过一次

在执行上下文堆栈仅包含平台代码之前,不得调用 onFulfilled 或 onRejected

onFulfilled 和 onRejected 必须被作为普通函数调用(即非实例化调用,这样函数内部 this 非严 格模式下指向 window)

then 方法可以被同一个 promise 调用多次

当 promise 成功执行时,所有 onFulfilled 需按照其注册顺序依次回调
当 promise 被拒绝执行时,所有的 onRejected 需按照其注册顺序依次回调

then方法必须返回一个 promise 对象 promise2 = promise1.then(onFulfilled, onRejected)

只要 onFulfilled 或者 onRejected 返回一个值 x ,promise 2 都会进入 onFulfilled 状态

如果 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回 拒因 e

如果 onFulfilled 不是函数且 promise1 状态变为已完成, promise2 必须成功执行并返回相 同的值

如果 onRejected 不是函数且 promise1 状态变为已拒绝, promise2 必须执行拒绝回调并返 回相同的据因

var promise1 = new Promise((resolve, reject) => {reject();});
promise1
  .then(null, function() {
    return 123;
  })
  .then(null, null)
  .then(null, null)
  .then(
    () => {
    console.log('promise2 已完成');
    },
    () => {
    console.log('promise2 已拒绝');
    });
    
    //promise2 已完成

promise解决方案

Promise 解决过程是一个抽象的操作,其需输入一个 promise 和一个值,我们表示为 [[Resolve]] (promise, x) (这句话的意思就是把 promise resolve 了,同时传入 x 作为值)

 promise.then(function(x) { 
  console.log('会执行这个函数,同时传入 x 变量的值', x);
 });
  • 如果 x 有 then 方法且看上去像一个 Promise ,解决程序即尝试使 promise 接受 x 用 x 的值来执行 promise 。

  • 如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise 如果 x 为 promise

    • 如果 x 处于等待态, promise 需保持为等待态直至 x 被执行或拒绝

    • 如果 x 处于执行态,用相同的值执行 promise

    • 如果 x 处于拒绝态,用相同的据因拒绝 promise

     var promise1 = function() {
      return new Promise(function(resolve) {
        setTimeout(function() {
          console.log(1);
          resolve();
    }, 1000) });
    }
    var promise2 = function() {
      return new Promise(function(resolve) {
        setTimeout(function() {
          console.log(2);
          resolve();
    }, 2000); });
    }
    promise1()
      .then(function() {
        return promise2(); // 此处返回一个 promise 实例 
      })
      .then(function() {
        console.log('已完成')
      }, function() {
        console.log('已拒绝')
      });
    
    • 如果 x 为 Object 或 function(不常⻅)
      • 首先尝试执行 x.then

      • 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise

      • 如果 then 是函数,将 x 作为函数的作用域 this 调用。传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise :

        • 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)
        • 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
        • 如果 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了多次,则优先 采用首次调用并忽略其他的调用如果调用 then 方法抛出了异常 e
        • 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略
        • 否则以 e 为据因拒绝 promise
      • 如果 then 不为函数,以 x 为参数将 promise 变为已完成状态

  • 如果 x 不为对象或者函数,以 x 为参数将 promise 变为已完成状态(重要且常⻅)

Promise 构造函数上的静态方法

Promise.resolve

返回一个 promise 实例,并将它的状态设置为已完成,同时将他的结果作为传入 promise 实例的值

 var promise = Promise.resolve(123);
promise
  .then(function(val) {
	console.log('已完成', val); 
  });
// 已完成 123

同样的, Promise.resolve 的参数也可以处理对象,函数等内容,处理方式和上面规范中介绍的相同。

Promise.reject

var promise = Promise.reject(123);
promise
  .then(null, function(val) {
	console.log('已拒绝', val); 
  });
// 已拒绝 123

Promise.all

返回一个 promise 实例,接受一个数组,里面含有多个 promise 实例,当所有 promise 实例都成为已完 成状态时,进入已完成状态,否则进入已拒绝状态。

var promise1 = function() {
  return new Promise(function(resolve) {
    setTimeout(function() {
      console.log(1);
      resolve();
	}, 1000)
 })
}

var promise2 = function() {
  return new Promise(function(resolve) {
    setTimeout(function() {
      console.log(2);
      resolve();
}, 2000); });
}
Promise.all([promise1(), promise2()])
  .then(function() {
	console.log('全部 promise 均已完成'); 
  });

注意,此时多个 promise 是同时进行的,也就是在上面这个例子中,等待 1s 打印 1 之后,再等待 1s 就 会打印 2 和「全部 promise 均已完成」。

Promise.race

返回一个 promise 实例,接受一个数组,里面含有多个 promise 实例,当有一个 promise 实例状态改变 时,就进入该状态且不可改变。这里所有的 promise 实例为竞争关系,只选择第一个进入改变状态的 promise 的值。

 var promise1 = function() {
  return new Promise(function(resolve) {
    setTimeout(function() {
      console.log(1);
      resolve(1);
}, 1000) });
}
var promise2 = function() {
  return new Promise(function(resolve) {
    setTimeout(function() {
      console.log(2);
      resolve(2);
}, 2000); });
}
Promise.race([promise1(), promise2()])
  .then(function(val) {
	console.log('有一个 promise 状态已经改变', val); 
  });

实现一个promise

//Promise 完整的实现
class Promise {
  callbacks = [];
  state = 'pending';//增加状态
  value = null;//保存结果
  constructor(fn) {
    fn(this._resolve.bind(this), this._reject.bind(this));
  }
  then(onFulfilled, onRejected) {
    return new Promise((resolve, reject) => {
      this._handle({
        onFulfilled: onFulfilled || null,
        onRejected: onRejected || null,
        resolve: resolve,
        reject: reject
      });
    });
  }
  catch(onError) {
    return this.then(null, onError);
  }
  finally(onDone) {
    if (typeof onDone !== 'function') return this.then();

    let Promise = this.constructor;
    return this.then(
      value => Promise.resolve(onDone()).then(() => value),
      reason => Promise.resolve(onDone()).then(() => { throw reason })
    );
  }
  static resolve(value) {
    if (value && value instanceof Promise) {
      return value;
    } else if (value && typeof value === 'object' && typeof value.then === 'function') {
      let then = value.then;
      return new Promise(resolve => {
        then(resolve);
      });

    } else if (value) {
      return new Promise(resolve => resolve(value));
    } else {
      return new Promise(resolve => resolve());
    }
  }
  static reject(value) {
    if (value && typeof value === 'object' && typeof value.then === 'function') {
      let then = value.then;
      return new Promise((resolve, reject) => {
        then(reject);
      });

    } else {
      return new Promise((resolve, reject) => reject(value));
    }
  }
  static all(promises) {
    return new Promise((resolve, reject) => {
      let fulfilledCount = 0
      const itemNum = promises.length
      const rets = Array.from({ length: itemNum })
      promises.forEach((promise, index) => {
        Promise.resolve(promise).then(result => {
          fulfilledCount++;
          rets[index] = result;
          if (fulfilledCount === itemNum) {
            resolve(rets);
          }
        }, reason => reject(reason));
      })

    })
  }
  static race(promises) {
    return new Promise(function (resolve, reject) {
      for (let i = 0; i < promises.length; i++) {
        Promise.resolve(promises[i]).then(function (value) {
          return resolve(value)
        }, function (reason) {
          return reject(reason)
        })
      }
    })
  }
  _handle(callback) {
    if (this.state === 'pending') {
      this.callbacks.push(callback);
      return;
    }

    let cb = this.state === 'fulfilled' ? callback.onFulfilled : callback.onRejected;

    if (!cb) {//如果then中没有传递任何东西
      cb = this.state === 'fulfilled' ? callback.resolve : callback.reject;
      cb(this.value);
      return;
    }

    let ret;

    try {
      ret = cb(this.value);
      cb = this.state === 'fulfilled' ? callback.resolve : callback.reject;
    } catch (error) {
      ret = error;
      cb = callback.reject
    } finally {
      cb(ret);
    }

  }
  _resolve(value) {
    if(this.state !== 'pending') return
    if (value && (typeof value === 'object' || typeof value === 'function')) {
      var then = value.then;
      if (typeof then === 'function') {
        then.call(value, this._resolve.bind(this), this._reject.bind(this));
        return;
      }
    }

    this.state = 'fulfilled';//改变状态
    this.value = value;//保存结果
    this.callbacks.forEach(callback => this._handle(callback));
  }
  _reject(error) {
    if(this.state !== 'pending') return
    this.state = 'rejected';
    this.value = error;
    this.callbacks.forEach(callback => this._handle(callback));
  }
}

Promise的一些执行题目

1、


Promise.resolve()
  .then(() => {
    return new Error('error!!!');
  })
  .then(res => {
    console.log('then:' + res);
  })
  .catch(err => {
    console.log('catch:' + err);
  })

输出结果: then:Error: error!!!

2、

let promise = new Promise((resolve, reject) => {
  resolve('success1');
  reject('error');
  resolve('success2');
})

promise
  .then((res) => {
    console.log('then:' + res);
  })
  .catch((err) => {
    console.log('catch:' + err);
  });

输出结果:then:success1

3、

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

promise.then(() => {
  console.log(3);
})
console.log(4);

输出结果:1 2 4 3

Promise 构造函数内部的执行器函数内部属于同步代码,.then 注册的回调函数属于微任务,那么会先输出同步代码 1,遇到 resolve() 并不会阻止后面同步代码的执行,因为并没有 return 语句。然后将微任务加入微任务队列,之后打印同步代码 2,之后继续先打印同步代码 4,最后取出微任务队列中的任务元素,打印 3,因此打印结果为 1 2 4 3。

4、

let p1 = new Promise((resolve, reject) => {
  reject(42);
});

p1.catch((function (value) {
  console.log(value);
  return value + 1;
})).then(function (value) {
  console.log(value);
});

输出结果: 42 43

5、

let p1 = new Promise((resolve, reject) => {
  resolve(42);
});

let p2 = new Promise((resolve, reject) => {
  reject(new Error('TypeError!!!'));
});

p1.then(function (value) {
  console.log(value);
  return p2;
}).then(function (value) {
  console.log(value);
}, function (err) {
  console.log(err);
})

输出结果: 42 Error: TypeError!!!

6、

setTimeout(() => {
  console.log('timer1');
  Promise.resolve().then(() => {
    console.log('promise1');
  })
})

Promise.resolve().then(() => {
  console.log('promise2');
  setTimeout(() => {
    console.log('timer2');
  })
})

输出结果:promise2 timer1; promise1 timer2

7、

Promise.resolve()
  .then(() => { // 外层第一个then
    Promise.resolve().then(() => {
      console.log(1);
    }).then(() => {
      console.log(2);
    })
  })
  .then(() => { // 外层第二个then
    console.log(3);
  })

输出结果: 1 3 2

先将外层第一个 then 压入微任务列表,等待这个 then 执行完返回一个 promise 之后再将外层第二个 then 压入微任务队列吧,第一个then里面的逻辑同样如此。

8、

async function async1() {
  await async2();
  console.log('async1 end');
}

async function async2() {
  console.log('async2 end');
}
async1();
console.log(10);

输出结果: async1 end 10 async2 end

9、

async function async1() {
  try {
    await async2();
  } catch (err) {
    console.log('async1 end');
    console.log(err);
  }
}

async function async2() {
  console.log('async2 end');
  return Promise.reject(new Error('error!!!'));
}
async1();
console.log(10);


输出结果:async2 end 10 async1 end error!!!

10、

async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2() {
    console.log('async2');
}
console.log('script start');
setTimeout(function() {
    console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {
    console.log('promise1');
    resolve();
}).then(function() {
    console.log('promise2');
});
console.log('script end');


输出结果: script start async1 start async2 promise1 script end async1 end promise2 setTimeout

11、

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{ pending } after1