这一次,彻底搞懂 Promise

175 阅读4分钟

1、描述

什么是 Promise?

  • Promise 是一个函数也是一个对象,它代表了一个异步操作的最终完成或者失败。

    这么简单?没错,就是这么简单,Promise 没你想的那么难。

2、三种状态

一个 Promise 必然处于以下几种状态之一:

  • 待定(pending): 初始状态,既没有被兑现,也没有被拒绝。
  • 已兑现(fulfilled): 意味着操作成功完成。
  • 已拒绝(rejected): 意味着操作失败。

注意:状态只能从 pendingfulfilled 或者 rejected,反之不行,也就是说,一旦状态发生改变就不能再改变了。

3、最基本的 Promise 例子

const p = new Promise((resolve, reject) => {
  // resolve 后状态就从 pending 变为 fulfilled
  resolve('成功的结果');
  // reject('失败的结果')(如果 reject ,那么状态就从 pending 变为 rejected )
});

4、then() 和 catch() 方法

看下面的例子:

const p = new Promise((resolve, reject) => {
  resolve('成功的结果');
})
  .then(res => {
    console.log(res); //成功的结果
  })
  .catch(rej => {
    console.log(rej);
  });

我们能从上面这段代码读到什么信息呢?

  1. 首先,resolve 后状态就从 pending 变为 fulfilled
  2. 状态变成 fulfilled 后就会执行 then 方法;
  3. then 方法中的参数 res 就是 resolve 传来的成功的结果,如果 resolve 时不传参数,那么 then 方法中的 res 就是undefined

再看这个例子:

const p = new Promise((resolve, reject) => {
  resolve('成功的结果');
})
  .then(res => {
    console.log(res); // 成功的结果
    return new Promise((resolve, reject) => {
      resolve('成功了');
    });
  })
  .catch(rej => {
    console.log(rej);
  })
  .then(res => {
    console.log(res); 
  });

注意:此时我在第一个 then 方法中又返回了一个 Promise

输出结果为:

成功的结果
成功了

为什么?

  1. 在第一个 then 中 返回一个 Promise,这个 Promise 调用了 resolve() 方法;
  2. 此时状态变为了 fulfilled,那么后面的 .catch() 肯定不会执行,跳过它,往下走;
  3. 遇到了 .then()fulfilled 状态对应的就是 .then(),所以肯定会执行 then(),又因为 resolve() 时带了参数,所以 res 参数为 成功了

5、默认返回 undefined

看下面的例子:

const p = new Promise((resolve, reject) => {
  resolve('成功的结果');
})
  .then(res => {
    console.log(res);
    new Promise((resolve, reject) => {
      resolve('成功了');
    });
  })
  .catch(rej => {
    console.log(rej);
  })
  .then(res => {
    console.log(res);
  });

输出结果为:

成功的结果
undefined

为什么?

  1. 首先在第一个 then 里面先打印 '成功的结果',这个没什么好说的;
  2. 然后代码往下执行,注意,此时 new Promise 前面并没有加 return ,也就是说第一个 then 里隐藏了一段代码:return undefined
  3. 此时状态变成了fulfilled,跳过catch(),执行then(),输出undefined

6、new Promise() 的其他写法

思考一下,有时候我们 new Promise() 的目的是什么?是不是只是为了给后面的 .then 或者 .catch 传递参数,那既然你只是想把返回结果作为参数传过去,你就可以写成下面这样:

const p = new Promise((resolve, reject) => {
  resolve('成功的结果');
})
  .then(res => {
    return Promise.resolve('成功了');
  })
  .then(res => {
    console.log(res); //成功了
  })
  .catch(rej => {
    console.log(rej);
  });

设计师看了,觉得还不够简单,于是他干脆直接返回一个字符串:

const p = new Promise((resolve, reject) => {
  resolve('成功的结果');
})
  .then(res => {
    return '我是 then 里的 res 哦';
  })
  .then(res => {
    console.log(res); //我是 then 里的 res 哦
  })
  .catch(rej => {
    console.log(rej);
  });

注意:如果直接 return 一个字符串或者数字,JS 引擎就会把它包装成 Promise 对象,并且自动执行 Promise.resolve(),所以会走 then 方法。

7、链式调用

既然 then()catch() 方法都返回一个 Promise,那么我就可以进行如下的操作:

const p = new Promise((resolve, reject) => {
  resolve('成功的结果');
})
  .then(res => {
    return new Promise((res, rej) => {
      res('我是第一次结果');
    });
  })
  .then(res => {
    console.log(res);
    return Promise.resolve('我是第二次结果');
  })
  .then(res => {
    console.log(res);
    return Promise.reject('我是第三次结果');
  })
  .catch(rej => {
    console.log(rej);
  });

相信聪明的小伙伴已经看出来了,这就是传说中的解决回调地狱的法宝——链式调用,通过这种 .then() 的方式非常优雅地解决了以前那种函数嵌套,且造成代码不易读以及维护难的问题,感谢Promise!

8、总结

  • Promise作为构造函数时,接收一个函数作为参数,这个函数的参数又接收两个函数作为参数,分别为 resolvereject

  • Promise作为对象时,有静态方法,比如 Promise.resolve()Promise.reject(),其中 resolve() 对应 .then()rejected() 对应 .catch()

  • Promise.prototype.then()Promise.prototype.catch(),这两个方法都会返回一个 Promise