串联 Promise
到此为止,Promise 貌似不过是个对组合使用回调函数与 setTimeout() 函数的增量改进,然而 Promise 的内容远比表面上所看到的更多。更确切地说,存在多种方式来将 Promise 串联在一起,以完成更复杂的异步行为。
每次对 then() 或 catch() 的调用实际上创建并返回了另一个 Promise,仅当前一个 Promise 被完成或拒绝时,后一个 Promise 才会被决议。研究以下例子:
let p1 = new Promise(function(resolve, reject) {
resolve(42);
});
p1.then(function(value) {
console.log(value);
}).then(function() {
console.log("Finished");
});
此代码输出:
42
Finished
对 p1.then() 的调用返回了第二个 Promise,又在这之上调用了 then()。仅当第一个 Promise 已被决议后,第二个 then() 的完成处理函数才会被调用。假若你在此例中不使用串联,它看起来就会是这样:
let p1 = new Promise(function(resolve, reject) {
resolve(42);
});
let p2 = p1.then(function(value) {
console.log(value);
})
p2.then(function() {
console.log("Finished");
});
在这个无串联版本的代码中,p1.then() 的结果被存储在 p2 中,并且随后 p2.then() 被调用,以添加最终的完成处理函数。正如你可能已经猜到的,对于 p2.then() 的调用也返回了一个 Promise,本例只是未使用此 Promise。
捕获错误
Promise 链允许你捕获前一个 Promise 的完成或拒绝处理函数中发生的错误。例如:
let p1 = new Promise(function(resolve, reject) {
resolve(42);
});
p1.then(function(value) {
throw new Error("Boom!");
}).catch(function(error) {
console.log(error.message); // "Boom!"
});
在此代码中,p1 的完成处理函数抛出了一个错误,链式调用指向了第二个 Promise 上的 catch() 方法,能通过此拒绝处理函数接收前面的错误。若是一个拒绝处理函数抛出了错误,情况也是一样:
let p1 = new Promise(function(resolve, reject) {
throw new Error("Explosion!");
});
p1.catch(function(error) {
console.log(error.message); // "Explosion!"
throw new Error("Boom!");
}).catch(function(error) {
console.log(error.message); // "Boom!"
});
此处的执行器抛出了一个错误,就触发了 p1 这个 Promise 的拒绝处理函数,该处理函数随后抛出了另一个错误,并被第二个 Promise 的拒绝处理函数所捕获。链式 Promise 调用能察觉到链中其他 Promise 中的错误。
为了确保能正确处理任意可能发生的错误,应当始终在 Promise 链尾部添加拒绝处理函数。
在 Promise 链中返回值
Promise 链的另一重要方面是能从一个 Promise 传递数据给下一个 Promise 的能力。传递给执行器中的 resolve() 处理函数的参数,会被传递给对应 Promise 的完成处理函数,这点你前面已看到过了。你可以指定完成处理函数的返回值,以便沿着一个链继续传递数据。例如:
let p1 = new Promise(function(resolve, reject) {
resolve(42);
});
p1.then(function(value) {
console.log(value); // "42"
return value + 1;
}).then(function(value) {
console.log(value); // "43"
});
p1 的完成处理函数在被执行时返回了 value + 1。由于 value 的值为 42(来自执行器),此完成处理函数就返回了 43。这个值随后被传递给第二个 Promise 的完成处理函数,并被其输出到控制台。
你能对拒绝处理函数做相同的事。当一个拒绝处理函数被调用时,它也能返回一个值。如果这么做,该值会被用于完成下一个 Promise,就像这样:
let p1 = new Promise(function(resolve, reject) {
reject(42);
});
p1.catch(function(value) {
// first fulfillment handler
console.log(value); // "42"
return value + 1;
}).then(function(value) {
// second fulfillment handler
console.log(value); // "43"
});
此处的执行器使用 42 调用了 reject(),该值被传递到这个 Promise 的拒绝处理函数中,从中又返回了 value + 1。尽管后一个返回值是来自拒绝处理函数,它仍然被用于链中下一个 Promise 的完成处理函数。若有必要,一个 Promise 的失败可以通过传递返回值来恢复整个 Promise 链。
在 Promise 链中返回 Promise
从完成或拒绝处理函数中返回一个基本类型值,能够在 Promise 之间传递数据,但若你返回的是一个对象呢?若该对象是一个 Promise,那么需要采取一个额外步骤来决定如何处理。研究以下例子:
let p1 = new Promise(function(resolve, reject) {
resolve(42);
});
let p2 = new Promise(function(resolve, reject) {
resolve(43);
});
p1.then(function(value) {
// first fulfillment handler
console.log(value); // 42
return p2;
}).then(function(value) {
// second fulfillment handler
console.log(value); // 43
});
在此代码中,p1 安排了一个决议 42 的作业,p1 的完成处理函数返回了一个已处于决议态的 Promise:p2。由于 p2 已被完成,第二个完成处理函数就被调用了。而若 p2 被拒绝,会调用拒绝处理函数(如果存在的话),而不调用第二个完成处理函数。
关于此模式需认识的首要重点是第二个完成处理函数并未被添加到 p2 上,而是被添加到第三个 Promise。正因为此,上个例子就等价于:
let p1 = new Promise(function(resolve, reject) {
resolve(42);
});
let p2 = new Promise(function(resolve, reject) {
resolve(43);
});
let p3 = p1.then(function(value) {
// first fulfillment handler
console.log(value); // 42
return p2;
});
p3.then(function(value) {
// second fulfillment handler
console.log(value); // 43
});
此处清楚说明了第二个完成处理函数被附加给 p3 而不是 p2。这是一个细微但重要的区别,因为若 p2 被拒绝,则第二个完成处理函数就不会被调用。例如:
let p1 = new Promise(function(resolve, reject) {
resolve(42);
});
let p2 = new Promise(function(resolve, reject) {
reject(43);
});
p1.then(function(value) {
// first fulfillment handler
console.log(value); // 42
return p2;
}).then(function(value) {
// second fulfillment handler
console.log(value); // never called
});
在此例中,由于 p2 被拒绝了,第二个完成处理函数就永不被调用。不过你可以改为对其附加一个拒绝处理函数:
let p1 = new Promise(function(resolve, reject) {
resolve(42);
});
let p2 = new Promise(function(resolve, reject) {
reject(43);
});
p1.then(function(value) {
// first fulfillment handler
console.log(value); // 42
return p2;
}).catch(function(value) {
// rejection handler
console.log(value); // 43
});
此处 p2 被拒绝,导致拒绝处理函数被调用,来自 p2 的拒绝值 43 会被传递给拒绝处理函数。
从完成或拒绝处理函数中返回 thenable,不会对 Promise 执行器何时被执行有所改变。第一个被定义的 Promise 将会首先运行它的执行器,接下来才轮到第二个 Promise 的执行器执行,以此类推。返回 thenable 只是让你能在 Promise 结果之外定义附加响应。你能通过在完成处理函数中创建一个新的 Promise,来推迟完成处理函数的执行。例如:
let p1 = new Promise(function(resolve, reject) {
resolve(42);
});
p1.then(function(value) {
console.log(value); // 42
// create a new promise
let p2 = new Promise(function(resolve, reject) {
resolve(43);
});
return p2
}).then(function(value) {
console.log(value); // 43
});
在此例中,一个新的 Promise 在 p1 的完成处理函数中被创建。这意味着直到 p2 被完成之后,第二个完成处理函数才会执行。若你想等待前面的 Promise 被解决,之后才去触发另一个 Promise,那么这种模式就非常有用。