Promise 是 ES6 中新增加的类,目的是为了管理 JS 中的异步编程的,所以我们也把它称为 Promise 设计模式。

就是一个承诺,就像点餐一样,现在还没做好,给你一个凭据,一会好了,再过来拿。

可以解决异步的一些问题。比如回调地狱。

基本用法

1. 创建实例

通过 new 来创建一个实例,每个实例都可以管理一个异步操作

  1. let p = new Promise(function(resolve, reject) {
  2. })
  • 必须传递一个回调函数,在回调函数中管理异步操作,不传递会报错

  • 可以给回调函数传入两个参数:

    • resolve:处理异步操作成功时,执行的函数

    • reject:处理异步操作失败时,执行的函数

  • new Promise 的时候,会立即把回调函数执行了(Promise 本身是同步的)

  1. let p = new Promise(function(resolve, reject) {
  2. //=> new Promise的时候,创建 Promise 的一个实例,立即把当前函数执行
  3. //=> Promise 本身是同步的,它可以管理异步操作
  4. setTimeout(()=>{
  5. reject(100);
  6. }, 1000);
  7. console.log(1);
  8. });
  9. console.log(2);
  10. //=> 1 2

如果代码执行中报错,会自动走失败回调。但是,如果在报错之前就执行了 resolve 函数,状态凝固,依然会走成功回调

2. 向成功和失败队列添加函数

基于 Promise.prototype.then 方法(还有 catch/finally),定义异步操作成功或者失败时的函数,独立进行管理。

  1. var p = new Promise((resolve, reject) => {
  2. $.ajax({
  3. url: 'data.json',
  4. success(result) {
  5. resolve(result);
  6. },
  7. error(msg) {
  8. reject(msg);
  9. }
  10. });
  11. });
  12. p.then(success, fail);
  13. function success(result) {
  14. console.log(reuslt);
  15. }
  16. function fail(mag) {
  17. console.log(mag);
  18. }

then 方法

实例的 then 方法,传入的两个参数,就是成功和失败执行的函数,也就是传入 Promise 的两个参数:resolvereject

  1. var p = new Promise((resolve, reject) => {
  2. //=> 可以手动执行,也可以让其自动执行这两个函数
  3. });
  4. p.then(success, fail);
  5. function success() {
  6. }
  7. function fail() {
  8. }

回调函数中的参数

第一个 then 中回调函数的参数,是 Promise 创建时调用这两个函数传入的参数,注意,这两个回调函数只支持一个参数的传递

后面的 then 中的两个回调函数,接受的参数是上一个 then 执行的函数返回的值

  • 普通值,那么接受的就是普通值

  • Promise 的实例,那么会把这个实例代替整个 then 的默认返回值,会等这个 Promise 管控的异步操作执行完成之后,再执行下一个 then,把这个异步操作的结果作为下一个 then的参数

  1. let p = new Promise((res, rej)=>{
  2. res({q:1});
  3. });
  4. p.then((data)=> {
  5. data.w = 2;
  6. return data;
  7. }, ()=>{
  8. return 12;
  9. }).then((data)=>{
  10. }, (data)=>{
  11. })

多个 then 的执行

多个 then的调用,不是依次把成功或者失败的所有方法执行。

异步操作成功或者失败,先把第一个 then中的方法执行,每执行一个 then会默认返回一个新的 Promise 实例,这个实例管控的是这个 then 中方法执行的成功还是失败。

如果上一个 then中返回的是一个具体值,而且执行中没有错误异常,会执行完同步代码之后,立即执行下一个 then 中的成功回调方法,而不会等待其异步代码执行完成(不写 return 也会返回具体值 undefined

如果返回的是一个 Promise 的实例(并且管控了一个异步操作),那么就只能等待 Promise 的异步操作完成,把成功后的结果当作具体值返回,才能进入下一个函数执行。

相当于把这个 Promise 实例代替整个 then 的默认返回值,下一个 then 管控的就是这个 Promise 实例

建议不要使用 then中的第二个参数(因为这样看起来较乱),而是建议我们使用 Promise.prototype.catch 方法来管理失败的情况

catch 方法

第一个 catch

  • 异步操作失败会执行它

  • 第一个 then 失败也会执行它

也就是说,最后的 catch 可以管控前面所有的操作,即当前整个 promise 链条。

如果上一组失败,那么就会执行 catch;如果上一组操作成功,但是执行 then 的过程中报错,那么也会执行 catch。

  1. var p = new Promise((resolve, reject) => {
  2. $.ajax({
  3. url: 'data.json',
  4. success(result) {
  5. resolve(result);
  6. },
  7. error(msg) {
  8. reject(msg);
  9. }
  10. });
  11. });
  12. p.then(result=>{
  13. console.log(result);
  14. return 100;
  15. }).catch(msg => {
  16. }).then(result=>{
  17. console.log(result);
  18. });

finally 方法

不管成功或者失败,都会执行的操作。较少使用。

状态凝固

Promise 有三种状态:pending(准备:初始化成功,开始执行异步的任务)、resolved(成功)、rejected(失败)。

  • 当实例创建完成时,状态 -> pending

  • 当 resolve 执行时,状态 pending -> resolved

  • 当 reject 执行时,状态 pending -> rejected

状态一旦从 pending -> resolved 或者 pending -> rejected,那么其状态就会凝固,不再允许改变状态。也就是说,resolve 或者 reject 只要有一个执行之后,另外一个就不会执行了。

  1. var p = new Promise((resolve, reject) => {
  2. //=> new 一个 Promise 时,会先执行所有的同步操作
  3. //=> 然后再执行放入等待中的异步操作以及成功或者失败的回调
  4. // 执行前面的,状态改变,后面的不执行
  5. reject();
  6. resolve(); //=> 会改变状态,不会执行
  7. console.log(1); //=> 这是同步操作,会先于 rej 和 res 执行
  8. //=> 执行前面的,状态改变,后面的不执行
  9. resolve();
  10. reject();
  11. })

Promise.all 方法

用于合并 Promise 的异步操作,传入的 Promise 的实例中的异步操作都执行完毕之后,才会执行后面的 then

接收一个数组作为参数,数组中的元素是 Promise 的实例。

  1. Promise.all([p1, p2]).then((res) => {
  2. conosle.log(res);
  3. }).catch((err)=>{
  4. console.log(err);
  5. })

后面的 then 中的成功回调函数中接收的参数也是一个数组,由每一个 Promise 实例异步操作的结果(传入 reject 或者 resolve 的参数)组成。

如果有一个发生错误或者失败,那么就会直接走 then 中的失败回调或者 catch,传入的参数只有一个错误信息,而不是数组。

Promise.race 方法

用于比较异步操作的快慢,传入的 Promise 的实例中的异步操作,哪一个先执行完毕,其他的就不会再执行,直接执行后面的 then中的回调。

与 all 方法一样,都是接收一个数组作为参数,数组中的元素是 Promise 实例

  1. Promise.race([p1, p2]).then((res) => {
  2. conosle.log(res);
  3. }).catch((err)=>{
  4. console.log(err);
  5. })

then 中的回调中接受的参数是那个先执行完毕的异步操作返回的数据或者错误信息

可用于 Ajax 请求的超时控制:

  1. const request = ajax('/api/user');
  2. const timeout = new Promise((resolve, reject) => {
  3. setTimeout(() => {
  4. reject(new Error('timeout'))
  5. }, 1500);
  6. })
  7. Promise.race([reqeust, timeout]);


全局异常捕获

可以全局监听一个 unhandledrejection 事件,来捕获那些没有被手动捕获的 promise 异常

  1. window.addEventListener('unhandledrejection', e => {
  2. const {reason, promise} = e;
  3. e.preventDefault();
  4. }, false);
  5. process.on('unhandledrejection', (reason, promise) => {
  6. })

:::danger 我们不应该依赖于全局的异常捕获,而是每一个都进行异常的处理。 :::