2.4. Promise#then

在前面的章节里我们对Promise基本的实例方法 thencatch 的使用方法进行了说明。

这其中,我想大家已经认识了 .then().catch() 这种链式方法的写法了,其实在Promise里可以将任意个方法连在一起作为一个方法链(method chain)。

promise可以写成方法链的形式

  1. aPromise.then(function taskA(value){
  2. // task A
  3. }).then(function taskB(vaue){
  4. // task B
  5. }).catch(function onRejected(error){
  6. console.log(error);
  7. });

如果把在 then 中注册的每个回调函数称为task的话,那么我们就可以通过Promise方法链方式来编写能以taskA → task B 这种流程进行处理的逻辑了。

Promise方法链这种叫法有点长(其实是在日语里有点长,中文还可以 —译者注),因此后面我们会简化为 promise chain 这种叫法。

Promise之所以适合编写异步处理较多的应用,promise chain可以算得上是其中的一个原因吧。

在本小节,我们将主要针对使用 then 的promise chain的行为和流程进行学习。

2.4.1. promise chain

在第一章 promise chain 里我们看到了一个很简单的 then → catch 的例子,如果我们将方法链的长度变得更长的话,那在每个promise对象中注册的onFulfilled和onRejected将会怎样执行呢?

promise chain - 即方法链越短越好。 在这个例子里我们是为了方便说明才选择了较长的方法链。

我们先来看看下面这样的promise chain。

promise-then-catch-flow.js

  1. function taskA() {
  2. console.log("Task A");
  3. }
  4. function taskB() {
  5. console.log("Task B");
  6. }
  7. function onRejected(error) {
  8. console.log("Catch Error: A or B", error);
  9. }
  10. function finalTask() {
  11. console.log("Final Task");
  12. }
  13. var promise = Promise.resolve();
  14. promise
  15. .then(taskA)
  16. .then(taskB)
  17. .catch(onRejected)
  18. .then(finalTask);

上面代码中的promise chain的执行流程,如果用一张图来描述一下的话,像下面的图那样。

promise-then-catch-flow

Figure 3. promise-then-catch-flow.js附图

上述代码 中,我们没有为 then 方法指定第二个参数(onRejected),也可以像下面这样来理解。

then

注册onFulfilled时的回调函数

catch

注册onRejected时的回调函数

再看一下 上面的流程图 的话,我们会发现 Task ATask B 都有指向 onRejected 的线出来。

这些线的意思是在 Task ATask B 的处理中,在下面的情况下就会调用 onRejected 方法。

  • 发生异常的时候

  • 返回了一个Rejected状态的promise对象

第一章 中我们已经看到,Promise中的处理习惯上都会采用 try-catch 的风格,当发生异常的时候,会被 catch 捕获并被由在此函数注册的回调函数进行错误处理。

另一种异常处理策略是通过 返回一个Rejected状态的promise对象 来实现的,这种方法不通过使用 throw 就能在promise chain中对 onRejected 进行调用。

关于这种方法由于和本小节关系不大就不在这里详述了,大家可以参考一下第4章 使用reject而不是throw 中的内容。

此外在promise chain中,由于在 onRejectedFinal Task 后面没有 catch 处理了,因此在这两个Task中如果出现异常的话将不会被捕获,这点需要注意一下。

下面我们再来看一个具体的关于 Task AonRejected 的例子。

Task A产生异常的例子

Task A 处理中发生异常的话,会按照TaskA → onRejected → FinalTask 这个流程来进行处理。

promise taska rejected flow

Figure 4. Task A产生异常时的示意图

将上面流程写成代码的话如下所示。

promise-then-taska-throw.js

  1. function taskA() {
  2. console.log("Task A");
  3. throw new Error("throw Error @ Task A")
  4. }
  5. function taskB() {
  6. console.log("Task B");// 不会被调用
  7. }
  8. function onRejected(error) {
  9. console.log(error);// => "throw Error @ Task A"
  10. }
  11. function finalTask() {
  12. console.log("Final Task");
  13. }
  14. var promise = Promise.resolve();
  15. promise
  16. .then(taskA)
  17. .then(taskB)
  18. .catch(onRejected)
  19. .then(finalTask);

执行这段代码我们会发现 Task B 是不会被调用的。

在本例中我们在taskA中使用了 throw 方法故意制造了一个异常。但在实际中想主动进行onRejected调用的时候,应该返回一个Rejected状态的promise对象。关于这种两种方法的异同,请参考 使用reject而不是throw 中的讲解。

2.4.2. promise chain 中如何传递参数

前面例子中的Task都是相互独立的,只是被简单调用而已。

这时候如果 Task A 想给 Task B 传递一个参数该怎么办呢?

答案非常简单,那就是在 Task A 中 return 的返回值,会在 Task B 执行时传给它。

我们还是先来看一个具体例子吧。

promise-then-passing-value.js

  1. function doubleUp(value) {
  2. return value * 2;
  3. }
  4. function increment(value) {
  5. return value + 1;
  6. }
  7. function output(value) {
  8. console.log(value);// => (1 + 1) * 2
  9. }
  10. var promise = Promise.resolve(1);
  11. promise
  12. .then(increment)
  13. .then(doubleUp)
  14. .then(output)
  15. .catch(function(error){
  16. // promise chain中出现异常的时候会被调用
  17. console.error(error);
  18. });

这段代码的入口函数是 Promise.resolve(1); ,整体的promise chain执行流程如下所示。

  1. Promise.resolve(1); 传递 1 给 increment 函数

  2. 函数 increment 对接收的参数进行 +1 操作并返回(通过return

  3. 这时参数变为2,并再次传给 doubleUp 函数

  4. 最后在函数 output 中打印结果

promise-then-passing-value

Figure 5. promise-then-passing-value.js示意图

每个方法中 return 的值不仅只局限于字符串或者数值类型,也可以是对象或者promise对象等复杂类型。

return的值会由 Promise.resolve(return的返回值); 进行相应的包装处理,因此不管回调函数中会返回一个什么样的值,最终 then 的结果都是返回一个新创建的promise对象。

关于这部分内容可以参考 专栏: 每次调用then都会返回一个新创建的promise对象 ,那里也对一些常见错误进行了介绍。

也就是说, Promise#then 不仅仅是注册一个回调函数那么简单,它还会将回调函数的返回值进行变换,创建并返回一个promise对象。