Promise的链式调用

152 阅读5分钟

前面我们说了,then方法会返回一个新的Promise实例对象,就说明,可以继续调用then方法,这就形成了一个链式调用。

Promise的链式调用

链式调用是很好的处理异步操作的方式,像以前没有Promise去处理多个异步任务的时候经常会遇到回调地狱的情况,后面会写一大串的代码,可读性很差而且也不方便使用,而Promise的链式调用就很好的处理了这个问题。

    let p = new Promise((resolve, reject) => {
      resolve("123");
    });
    p.then((res) => {
      console.log(res);
    }).then((res) => {
      console.log(res);
    });

要想用好链式调用首先得要知道then方法和catch方法的返回结果是怎样的,我们只有知道了新的Promise实例对象的状态和结果才能够进行后续的调用,不过这个我们在上篇文章里已经说了,现在来说一些别的情况。

第一个情况是:没有相关的后续处理,后边的任务和前面的任务保持一致。

    let p = new Promise((resolve, reject) => {
      resolve("123");
    });
    let newP = p.then((res) => {});
    console.log(newP);

看这段代码,我们知道newP的状态为fulfilled,结果为undefined,这是有后续处理的情况,这里所说的后续处理是什么呢?就是这个p.then()方法的第一个回调函数,因为p的状态为fulfilled,所以调用的是成功的回调函数,所以p.then()方法的第一个回调函数就是p的后续处理。

但是如果没有后续处理呢?我们把代码修改一下:

    let p = new Promise((resolve, reject) => {
      resolve("123");
    });
    let newP = p.then(null, (res) => {});
    console.log(newP);

这里p.then()方法只有失败的回调函数,没有成功的回调函数,所以p就没有后续处理了,这个时候p.then()方法返回的实例对象newP会跟p是一样的。这就是没有相关的后续处理,后边的任务和前面的任务保持一致。

1740571364324.png

换一种情况也是如此:这里p的状态是失败的,所以要调用失败的回调函数,但是没有对应的后续处理,所以就newP就和p保持一致。

    let p = new Promise((resolve, reject) => {
      reject("123");
    });
    let newP = p.then((res) => {});
    console.log(newP);

1740571462258.png

第二种情况是:有相关处理,但是还没有执行,新任务就挂起。

有相关处理,但是还没有执行这句话什么意思呢?就是p有后续处理,但是p还没有执行结束,也就是状态还未改变,此时不会调用对应的回调函数,所以叫做还没有执行。

    let p = new Promise((resolve, reject) => {
      setTimeout(() => {
        reject("123");
      }, 3000);
    });
    let newP = p.then(null, (res) => {});
    console.log(newP);

比如上面这段代码,p要在3秒以后才会改变状态,但是根据JS执行顺序,下面的let newP = p.then(null, (res) => {})会先执行(有人可能会问,不是改变状态以后才会执行then方法吗?不是这么理解的,是改变状态以后才会调用对应的回调函数,then方法是按照JS执行顺序执行的,这一点后面手写Promise的时候会详细讲到。),然后打印log,此时的newP的状态就是pending,结果为undefined,这就是所说的新任务挂起。

image.png

等3秒以后p的状态改变了,进行了后续处理,newP的状态和结果才会改变,根据上篇文章说的then方法的返回,这里没进行抛出错误也没有return,可以看作return undefined,所以newP改变后的状态应该为fulfilled,结果为undefined。当然如果没有后续处理那就是第一种情况了。

1740575125751.png

第三种情况:是有后续处理,并且已经执行,也就是上篇文章说的then方法的返回结果有哪几种情况

不明白的看上篇文章去。

Promise的链式调用在实际项目中的使用

我们在实际项目使用Promise的时候一般都会这样:

    let p = new Promise((resolve, reject) => {
      // 执行操作
    });
    p.then((res) => {}).catch((err) => {});

这也是链式调用,用then去处理成功的回调函数,用catch去处理失败的回调函数。为什么能够这么写是因为如果p状态是成功的就会直接调用then方法的第一个回调函数,如果是失败的,then方法没有第二个回调函数去处理,就出现了第一种情况,然后then方法就会返回跟p一样的实例对象,然后再去调用catch方法。

如果有多个连续的任务需要处理,那么就会去这么写:

    let p = new Promise((resolve, reject) => {
      // 执行操作
    });
    p.then((res) => {})
      .then((res) => {})
      .then((res) => {})
      .catch((err) => {});

如果p状态是成功的就会按照顺序进行处理,一步一步执行then方法,如果p状态是失败的或者执行then方法的过程中失败了,就会根据第一种情况去调用catch方法,这种情况也叫做异常穿透

如何中断Promise链

通过上文我们知道,链式调用中then方法或者catch方法返回的几种情况,如果是return出去一个非Promise实例对象,也会转换成一个成功状态的Promise实例对象,这个时候又会继续调用后续的处理,如果是return出去Promise实例对象,会根据Promise实例对象的状态去调用后续的处理,那么怎么才能中断这个Promise链呢?

其实在讲第二种情况的时候已经知道个大概了,就是让Promise实例对象的状态处于pending,这样就会去调用后续处理,也就是中断Promise链了。

而让前面的Promise实例对象处于pending,就只有让前面的处理去return new Promise(() => {})

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

    p.then((value) => {
      return "success";
    })
      .then((value) => {
        console.log(value); // success
        return new Promise(() => {});
      })
      .then((value) => {
        console.log(value); // 不执行
      })
      .catch((reason) => {
        console.warn(reason);
      });

p为成功状态的实例对象,所以调用第一个then方法的回调函数,然后返回的也是一个成功状态的Promise实例对象,然后再去调用第二个then方法,此时返回的一个pending状态的实例对象,这时没有改变状态也就不会调用后续的处理,也就是中断Promise链。