什么是Promise

185 阅读12分钟

什么是Promise

当我们说到Promise的时候可能会有两个意思,第一个是Promise A+是一套民间规范,第二个就是ES6 Promise构造函数。这两个我们都来说一下。

Promise A+是在2015年也就是ES6之前就出现的,那时候的异步处理就比较混乱,于是社区就自行出了这么一套Promise A+规范去处理异步,统一了异步的处理模型,解决了回调地狱的问题,让不同的人之间的代码可以相互操作。Promise A+规范文档里开头就介绍了凡是带then方法的对象都是Promise:

const obj = {
  then() {},
};
// 这就是一个Promise

// 或者说一个数组,数组也是对象
const arr = [];
arr.then;
// 那么这也是一个Promise

// 再或者说一个函数,函数也是对象
const fun = function () {};
fun.then;
// 那么这也是一个Promise

ES6的Promise是一个构造函数,通过这个构造函数new出来的对象就是满足Promise A+规范的对象,也有一个then方法,而且ES6对Promise还加了一些方法比如catch方法,finally方法,还有一些静态方法比如Promise.all,Promise.race,Promise.allsettled

我们这里主要讲的是ES6的Promise构造函数。

Promise的基本使用

Promise构造函数

Promise构造函数:Promise(excutor) {}

  • excutor:是一个执行函数(resolve,reject) => {},该执行函数里面也有两个参数:
    • resolve:内部定义成功时调用的函数,可以传一个实参给then方法的onResolved函数。
    • reject:内部定义失败时调用的函数,可以传一个实参给then方法的onRejected函数。

我们在进行new这个Promise构造函数去创建实例对象的时候,会在执行函数里进行一个条件判定(因为Promise一般都是处理异步问题,根据是否满足条件去调用回调函数),如果满足条件就去调用resolve函数,在不满足条件的时候调用reject函数,至于调用这两个函数有什么作用,是去修改Promise的状态和结果的,也是指定Promise实例对象的回调函数的,下面会讲到这一点,现在知道执行函数里面做什么,又什么时候调用resolve函数和reject函数就好了。

const p = new Promise((resolve, reject) => {
  if (满足) {
    resolve();
  } else {
    reject();
  }
});

有一个比较重要的点就是,excutor执行函数会在Promise内部立即同步调用,就是说在开始new的时候执行函数就已经开始执行了,这点在实际应用中很重要。

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

1739894160133.png

先打印1后打印2就说明执行函数是同步调用的。

then方法

then方法是用来调用回调函数的,我们在前面判断完是成功还是失败以后,就要在then方法里面去调用回调函数,then方法里面会有两个参数,这两个参数都是回调函数,至于调用哪一个,就要根据Promise实例对象的状态去判断了,也就是根据前面调用的是resolve函数还是reject函数了。

Promise.prototype.then(onResolved,onRejected)

  • onResolved:成功的回调函数 (value)=>{},接收一个形参,就是resolve函数传的值。
  • onRejected:失败的回调函数 (reason)=>{},接收一个形参,就是reject函数传的值。

如果执行函数里面调用的是resolve函数,那么then方法里面就会去调用第一个函数参数onResolved,如果执行函数里面调用的是reject函数,那么then方法里面就会去调用第二个函数参数onRjected

而且then方法会返回一个新的Promise对象,这个后面会讲。

做个小案例感受一下:设计一个小功能,点击按钮生成随机数,如果随机数小于30就弹出一个成功。

<body>
  <div>
    <h2>Promise初体验</h2>
    <h2>点击按钮,30%概率中奖</h2>
    <button id="btn">点击抽奖</button>
  </div>
</body>
<script>
  function rand(m, n) {
    return Math.ceil(Math.random() * (n - m + 1)) + m - 1;
  }
  const btn = document.getElementById("btn");
  btn.addEventListener("click", () => {
    const p = new Promise((resolve, reject) => {
      let n = rand(1, 100);
      if (n <= 30) {
        resolve(); 
      } else {
        reject(); 
      }
    });
    p.then(
      () => {
        alert("中奖了");
      },
      () => {
        alert("再接再厉");
      }
    );
  });
</script>

Promise的状态和结果

Promise实例对象有两个比较重要的属性,一个是状态PromiseState,另外一个就是结果PromiseResult

状态

状态PromiseState有三个值:

  • pending:初始化未决定的。
  • resolved/fullfilled:成功的。
  • rejected:失败的。

刚开始创建Promise实例对象的时候状态是还未修改的,为pending,调用resolve函数以后会将状态修改为resolved或者fullfilled表示成功,调用reject函数会将状态修改为rejected表示失败。

1739866142825.png

需要注意的是状态只能由pending修改为fullfilled或者rejected,而且是不可逆的,就是说第一次修改完状态以后是不能再被第二次修改的。

1739867346643.png

结果

Promise实例对象还有一个属性叫做结果PromiseResult,是对象成功或者失败的结果,也就是resolve函数和reject函数传递的值,也就是传给onResolved函数和onRejected函数的值。

1739885606919.png

结果Promise只能被resolve函数和reject函数修改。

catch方法

前面说了一个then方法是用来指定回调函数的,then方法里有两个函数参数,所以需要通过前面Promise实例对象的状态去判断调用哪一个,如果是成功的就调用第一个onResolved函数,如果是失败就调用第二个onRejected函数,catch方法跟then差不多,只不过只有一个函数参数,是失败的时候调用的。

Promise.prototype.catch(onRejected)

  • onRejected:失败的回调函数 (reason)=>{},接收一个形参,就是reject函数传的值。

举个例子:

    const p = new Promise((resolve, reject) => {
      reject("我失败了");
    });

    p.catch((reason) => {
      console.log(reason);
    });

1739886998875.png

所以为了方便也为了好区分,我们会在then方法里只写成功的回调函数,在catch方法里写失败的回调函数。

    p.then((value) => {
      console.log(value);
    }).catch((reason) => {
      console.log(reason);
    });

Promise的工作流程

通过Promise构造函数创建一个实例对象,在Promise内部去执行一个异步操作,前面说执行函数是同步的,这里的异步操作指的是在执行函数里去执行异步操作(一般Promise就是去处理异步问题的),如果是成功的,就调用resolve函数,将Promise实例对象状态改为成功,然后调用then方法的第一个回调函数,返回一个新的Promise实例对象。如果是失败的,就调用reject函数,将Promise实例对象状态改为失败,然后调用then方法的第二个回调函数或者catch方法的回调函数,返回一个新的Promise实例对象。

                              => 成功 执行resolve() =>将Promise实例对象状态改为resolved => 调用 then 的第一个回调函数
new Promise() => 执行异步操作                                                                                     => 返回一个新的Promise对象
                              => 失败 执行reject()  =>将Promise对象状态改为rejected => 调用 then 的第二个回调函数

Promise的静态方法 resolve方法

这里的resolve方法和前面说的new Promise((resolve,reject) => {})中的resolve函数不同,这里的是作用于构造函数Promise的静态方法,用于快速创建一个Promise实例对象。

Promise.resolve()

  • 返回一个成功/失败的Promise对象。
  • 接收一个参数,分为四种情况。
    • 如果传入一个非Promise类型对象,则返回为一个Promise实例对象,状态为成功,结果值就是传入的参数值;
    • 如果传入的是一个Promise类型的对象,会直接返回这个Promise类型的对象。
    • 如果传入的是一个thenable对象,thenable对象就是一个带有then方法的对象,resolve会将这个对象直接转化为Promise对象,然后执行thenable对象的then方法。
    • 如果不传入参数,会返回一个状态为成功的Promise实例对象,结果为undefined。

下面我们看几个案例:

当传入的参数是一个非Promis类型对象,比如数字,字符串,数组,普通对象

    let p1 = Promise.resolve("于家宝");
    let p2 = Promise.resolve(18);
    let p3 = Promise.resolve([1, 2, 3]);
    let p4 = Promise.resolve({ a: 1 });
    console.log(p1);
    console.log(p2);
    console.log(p3);
    console.log(p4);

1739889004145.png

可以看到返回的是一个Promise对象,状态都是成功的,然后结果值就是我们传入的参数本身。

当传入的参数为一个Promise类型对象

    let p1 = new Promise((resolve, reject) => {
      resolve("我成功了");
    });
    let p2 = Promise.resolve(p1);
    console.log(p2, "传入成功的Promise对象");

    let p3 = new Promise((resolve, reject) => {
      reject("我失败了");
    });
    let p4 = Promise.resolve(p3);
    console.log(p4, "传入失败的Promise对象");

    let p5 = new Promise((resolve, reject) => {});
    let p6 = Promise.resolve(p5);
    console.log(p6, "传入pending的Promise对象");

1739890000840.png

可以看到是将这个Promise对象直接返回,不做任何变动。

至于为什么会有一个报错,是因为里面有一个失败的Promise对象p3,但是没有用then方法的第二个回调函数或者catch方法的回调函数去接收这个错误。

我们只需要使用then方法的第二个回调函数或者catch方法的回调函数去接收这个错误就不会在后台报错了。

    let p3 = new Promise((resolve, reject) => {
      reject("我失败了");
    });
    p3.then(
      (value) => {},
      (error) => {}
    );

    // 或者

    p3.catch((error) => {});

这个时候不知道会不会有人提出,那p4不也是一个失败的Promise对象吗,为什么不需要去接收p4的错误,我暂时也不知道,我猜想是因为p3和p4是同一个Promise对象,所以只需要接收一个就好了,后面我也做了测试去接收p4的错误而不接收p3的,后台也没有报错,我感觉我猜对了,如果有人知道原因可以一起讨论下。

当传入的参数为thenable对象

首先先讲一下什么是thenable对象,thenable对象就是一个带有then方法的对象,比如:

    let thenable = {
      name: "yujiabao",
      then: function(resolve, reject) {},
    };

Promise.resolve()方法会将thenable对象转化为Promise实例对象,状态和结果由thenable对象的then方法决定,如果then方法里执行的是resolve函数,状态就是成功的,如果执行reject函数,状态就是失败的,结果为函数传的值。

    let thenable = {
      name: "yujiabao",
      then: function(resolve, reject) {
        resolve(this.name);
      },
    };
    let p = Promise.resolve(thenable);
    console.log(p);

1739893145968.png

当不传入参数

    let p = Promise.resolve();
    console.log(p);

1739893241453.png

不传入参数的时候会返回一个状态为成功的Promise对象,结果为undefined。就像是第一种情况,传入的非Promise对象,传入的是一个undefined,返回的是一个Promise对象,状态是成功的,然后结果值就是我们传入的参数本身。

Promise的静态方法 reject方法

同样这里的reject方法和new Promise((resolve,reject) => {})中的reeject函数不同。

Promise.reject()

  • 无论参数是什么,都会返回一个失败的Promise对象,结果值就是参数本身。

做几个案例:

    // 传入非Promise对象,比如数字,数组,字符串,对象
    let p1 = Promise.reject(123)
    let p2 = Promise.reject([1, 2, 3])
    let p3 = Promise.reject('123')
    let p4 = Promise.reject({ a: 1 })
    console.log(p1, 'p1')
    console.log(p2, 'p2')
    console.log(p3, 'p3')
    console.log(p4, 'p4')
    console.log('---------------------------------------')
    // 传入Promise对象
    let pResolve = Promise.resolve('我是成功的Promise')
    let p5 = Promise.reject(pResolve)
    console.log(p5, 'p5')
    let pReject = Promise.reject('我是失败的Promise')
    let p6 = Promise.reject(pReject)
    console.log(p6, 'p6')

    // 传入thenable对象
    let thenable = {
      name: 'yujiabao',
      then: function (resolve, reject) {
        resolve(this.name)
      }
    }

    let p7 = Promise.reject(thenable)
    console.log(p7, 'p7')

1740060455338.png

看上面的案例,p1,p2,p3,p4传的都是非Promise类型对象,返回的错误状态的Promise实例对象,结果值为参数本身;p5,p6传的是不同状态的Promise实例对象,返回的都是错误状态的Promise实例对象,结果还是参数本身;p7传的值thenable对象,返回的还是错误状态的Promise实例对象,结果还是参数本身。

这一点跟resolve方法不太一样,要注意区分。

Promise的静态方法 all方法

Promise.all()方法用于将多个Promise实例对象包装成一个Promise实例对象。

接收一个参数为数组,数组里的每一项都是Promise实例对象,如果有不是Promise实例对象的会先去调用resolve方法将其快速的转换为Promise实例对象再进行处理。

Promise.all()返回的实例对象的状态由数组所有的实例对象决定,如果所有的实例对象的状态都是成功的,那么Promise.all()返回的实例对象的状态就是成功的,结果是数组所有实例对象的结果的集合。如果有一个是失败的,Promise.all()返回的实例对象的状态就是失败的,结果就是第一个失败的Promise实例对象的结果。

    let p1 = 'yujiabao'
    let p2 = Promise.resolve('我是成功的Promise实例对象')
    let p3 = Promise.reject('我是失败的Promise实例对象')

    let p = Promise.all([p1, p2, p3])
    console.log(p)

image.png

    let p1 = 'yujiabao'
    let p2 = Promise.resolve('我是成功的Promise实例对象')
    let p3 = Promise.resolve('我也是成功的Promise实例对象')

    let p = Promise.all([p1, p2, p3])
    console.log(p)

1740061300134.png

Promise的静态方法 race方法

Promise.race()方法同样用于将多个Promise实例对象包装成一个Promise实例对象。和Promise.all()方法不同的是,Promise.race()方法返回的实例对象的状态和结果就是第一个率先改变状态的Promise实例对象的状态和结果。

    let p1 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('ok')
      }, 1000)
    })
    let p2 = Promise.resolve('success')
    let p3 = Promise.resolve('oh yeah')
    const p = Promise.race([p1, p2, p3])
    console.log(p)

1740064631349.png

看代码这里第一个完成状态改变的是p2,所以Promise.race()方法返回的Promise实例对象的状态和结果就是p2的状态和结果.

这个方法可以用于如果指定时间内没有获得结果就报错。

    const p = Promise.race([
      fetch('/resource-that-may-take-a-while'),
      new Promise(function (resolve, reject) {
        setTimeout(() => reject(new Error('request timeout')), 5000)
      })
    ])
    p.then(console.log).catch(console.error)

Promise的静态方法 allSettled方法

Promise.allSettled()方法亦是同样用于将多个Promise实例对象包装成一个Promise实例对象。也是接收一个数组参数,每一项都是一个Promise实例对象,如果不是就先调用Promise.resolve()方法转换一下,和all方法和race方法不同的是,allSettled方法会等数组所有的实例对象都改变状态以后才会返回一个新的Promise实例对象,而且该实例对象只能是成功的也就是状态只能是fulfilled,结果是一个数组,数组的每一项都是一个对象,有一个属性叫做state,对应的是参数数组的每一个实例对象的状态,如果状态是成功的就为fulfilled,然后就会有另外一个属性value存放结果,如果是失败的就为rejected,就有另外一个属性reason存放结果。

    let p1 = 1;
    let p2 = Promise.resolve(2);
    let p3 = Promise.reject(3);

    let p = Promise.allSettled([p1, p2, p3]);
    console.log(p);

1740401982028.png