Javascript之Promise

Edit

Promise的意思是约定,相对应的词是Defer,是推迟。他的用意是在异步的JavaScript世界引入同步写法。看看下面的“邪恶金字塔”。

var fs = require('fs');
fs.readFile('sample01.txt', 'utf8', function (err, data) {
fs.readFile('sample02.txt', 'utf8', function (err,data) {
fs.readFile('sample03.txt', 'utf8', function (err, data) {
fs.readFile('sample04.txt', 'utf8', function (err, data) {

});
});
});
});

既难看又难用。有了Promise以后,这种嵌套的金字塔代码就变成平级的链式代码。而且可以保证调用的顺序。

JavaScript官方在2013年正式宣布支持Promise语法,尽管在很多浏览器端,仍然有不兼容的情况出现。不过可见这是大势所趋。在官方宣布支持之前,已经有很多第三方库做了polyfill,就是对原有函数进行了包装,以使其支持Promise。例如下面的库:

  • Q
  • when
  • WinJS
  • RSVP.js

上面这些库和 JavaScript 原生 Promise 都遵守一个通用的、标准化的规范:Promises/A+。jQuery的Promise-Defer的实现不符合这个标准,使用需谨慎。

另外各种Promise的实现虽概念一致,但仍不尽相同。看了很多文章,仍然云里雾里。刚开始接触的是这一篇文章:

Promise的术语

  • 肯定(fulfilled)
    • 该 Promise 对应的操作成功了
  • 否定(rejected)
    • 该 Promise 对应的操作失败了
  • 等待(pending)
    • 还没有得到肯定或者否定结果,进行中
  • 结束(settled)
    • 已经肯定或者否定了
  • 类 Promise (thenable)
    • 一个对象是否是“类 Promise”(拥有名为“then”的方法)的。

初始状态是pending,settled=fulfilled or rejected,状态转换只有一次,而且不能逆转。

var promise = new Promise(function(resolve, reject) {
// 做一些异步操作的事情,然后……

if (/* 一切正常 */) {
resolve("Stuff worked!");
}
else {
reject(Error("It broke"));
}
})

promise.then(function(result) {
console.log(result); // “完美!”
}, function(err) {
console.log(err); // Error: "出问题了"
});;

构造函数

构造器接受一个函数作为参数,它会传递给这个回调函数两个变量 resolve 和 reject。在回调函数中做一些异步操作,成功之后调用 resolve,否则调用 reject。

上面这段话的意思是,

  • 首先,Promise对象用一个回调函数来构造。
  • 其次,看下面这段code
    • 这个回调函数有两个参数,这两个参数由Promise来负责传递(resolve& reject)。
    • 回调函数中定义一些自己需要做的异步操作(asyncOp)。
    • 异步操作结束时,自行判断成功失败,失败调用reject函数,成功调用resolve函数。
    • Promise使用时至少与一个then配对,在then里面做asyncOp之后想做的事情。Promise里面的结果通过resolve和reject传递的参数(err&res)传到then里。
function polyFill(){
return new Promise(function (resolve, reject){
fs.asyncOp(function (err, res){
if (err)
reject(err);
else
resolve(res);
});
});
}
polyFill().then(resolveFunc, rejectFunc);

resolve & reject

这两个是Promise类的成员函数,使用的时候,也可以像下面这样调用:

promise.resolve(val).then(...);
promise.reject(Error(err)).then(...);

这样的调用和下面的常规调用是等效的:

var promise = Promise(function(resolve, reject){
if (good){
resolve(val);
}
else {
reject(Error(err));
}
});
promise.then(...);

各路贴子和解说中都鲜少提及resolve和reject函数的参数是什么,有什么用。我之前引用的两个链接都有说到,但我感觉讲的都不是很清楚。下面理一下我的思路:

resolve

  • resolve总是返回一个Promise对象
  • 接收到promise对象参数的时候, 返回的还是接收到的promise对象,而且下一个then会等到这个Promise定义的异步操作结束时才会运行。
  • 接收到thenable类型的对象的时候, 返回一个新的promise对象,这个对象具有一个 then 方法
  • 接收的参数为其他类型的时候(包括JavaScript对或null等), 返回一个将该对象作为值的新promise对象,这个值会被当作参数传到下一个定义了resolve函数参数的then里面。
    reject
    • reject与resolve类似,返回的也是一个Promise对象
    • reject通常传入一个Error()对象。这不是必须的,但Error对象包含了调用栈,方便调试。

错误处理

上面提到了reject已经涉及到一部分错误处理了。在Promise里面,我们还可以用catch。

get('story.json').then(function(response) { 
console.log("Success!", response);
}).catch(function(error) {
console.log("Failed!", error);
});

JavaScript Promises这里提到:

这里的 catch 并无任何特殊之处,只是 then(undefined, func) 的语法糖衣,更直观一点而已。catch相当于:

get('story.json').then(function(response) {
console.log("Success!", response);
}).then(undefined, function(error) {
console.log("Failed!", error);
});

总结一下:
在Promise中的任何错误,包括:

  • reject调用
  • Promise中的抛出的异常

会落到Promise后面第一个定义了reject函数参数的then分支里。

创建序列

JavaScript Promises中,详细解释了如何操作一组Promise,从真正的用一组Promise到使用Promise.all。这里讲的很好了,我直接拷过来。

  1. 最直接的数组forEach
// 从一个完成状态的 Promise 开始
var sequence = Promise.resolve();

// 遍历所有章节的 url
story.chapterUrls.forEach(function(chapterUrl) {
// 从 sequence 开始把操作接龙起来
sequence = sequence.then(function() {
return getJSON(chapterUrl);
}).then(function(chapter) {
addHtmlToPage(chapter.html);
});
});
  1. array.reduce 精简一下上面的代码:
// 遍历所有章节的 url
story.chapterUrls.reduce(function(sequence, chapterUrl) {
// 从 sequence 开始把操作接龙起来
return sequence.then(function() {
return getJSON(chapterUrl);
}).then(function(chapter) {
addHtmlToPage(chapter.html);
});
}, Promise.resolve());
  1. 我们希望同时下载所有章节,全部完成后一次搞定,正好就有这么个 API:
Promise.all(arrayOfPromises).then(function(arrayOfResults) {
//...
});

然而这样还是有提高空间。当第一章内容加载完毕我们可以立即填进页面,这样用户可以在其他加载任务尚未完成之前就开始阅读;当第三章到达的时候我们不动声色,第二章也到达之后我们再把第二章和第三章内容填入页面,以此类推。

  1. 为了达到这样的效果,我们同时请求所有的章节内容,然后创建一个序列依次将其填入页面:
getJSON('story.json').then(function(story) {
addHtmlToPage(story.heading);

// 把章节 URL 数组转换成对应的 Promise 数组
// 这样就可以并行加载它们
return story.chapterUrls.map(getJSON)
.reduce(function(sequence, chapterPromise) {
// 使用 reduce 把这些 Promise 接龙
// 以及将章节内容添加到页面
return sequence.then(function() {
// 等待当前 sequence 中所有章节和本章节的数据到达
return chapterPromise;
}).then(function(chapter) {
addHtmlToPage(chapter.html);
});
}, Promise.resolve());
}).then(function() {
addTextToPage("All done");
}).catch(function(err) {
// 捕获过程中的任何错误
addTextToPage("Argh, broken: " + err.message);
}).then(function() {
document.querySelector('.spinner').style.display = 'none';
});
%23Javascript%u4E4BPromise%0A@%28%u5B66%u4E60%u7B14%u8BB0%29%5Bjavascript%2C%20promise%5D%0A%0A%5BTOC%5D%0A%0APromise%u7684%u610F%u601D%u662F%u7EA6%u5B9A%uFF0C%u76F8%u5BF9%u5E94%u7684%u8BCD%u662FDefer%uFF0C%u662F%u63A8%u8FDF%u3002%u4ED6%u7684%u7528%u610F%u662F%u5728%u5F02%u6B65%u7684JavaScript%u4E16%u754C%u5F15%u5165%u540C%u6B65%u5199%u6CD5%u3002%u770B%u770B%u4E0B%u9762%u7684%u201C%u90AA%u6076%u91D1%u5B57%u5854%u201D%u3002%0A%60%60%60javascript%0Avar%20fs%20%3D%20require%28%27fs%27%29%3B%0Afs.readFile%28%27sample01.txt%27%2C%20%27utf8%27%2C%20function%20%28err%2C%20data%29%20%7B%0A%20%20%20%20fs.readFile%28%27sample02.txt%27%2C%20%27utf8%27%2C%20function%20%28err%2Cdata%29%20%7B%0A%20%20%20%20%20%20%20%20fs.readFile%28%27sample03.txt%27%2C%20%27utf8%27%2C%20function%20%28err%2C%20data%29%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20fs.readFile%28%27sample04.txt%27%2C%20%27utf8%27%2C%20function%20%28err%2C%20data%29%20%7B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%29%3B%0A%20%20%20%20%20%20%20%20%7D%29%3B%0A%20%20%20%20%7D%29%3B%0A%7D%29%3B%0A%60%60%60%0A%u65E2%u96BE%u770B%u53C8%u96BE%u7528%u3002%u6709%u4E86Promise%u4EE5%u540E%uFF0C%u8FD9%u79CD%u5D4C%u5957%u7684%u91D1%u5B57%u5854%u4EE3%u7801%u5C31%u53D8%u6210%u5E73%u7EA7%u7684%u94FE%u5F0F%u4EE3%u7801%u3002%u800C%u4E14%u53EF%u4EE5%u4FDD%u8BC1%u8C03%u7528%u7684%u987A%u5E8F%u3002%0A%0AJavaScript%u5B98%u65B9%u57282013%u5E74%u6B63%u5F0F%u5BA3%u5E03%u652F%u6301Promise%u8BED%u6CD5%uFF0C%u5C3D%u7BA1%u5728%u5F88%u591A%u6D4F%u89C8%u5668%u7AEF%uFF0C%u4ECD%u7136%u6709%u4E0D%u517C%u5BB9%u7684%u60C5%u51B5%u51FA%u73B0%u3002%u4E0D%u8FC7%u53EF%u89C1%u8FD9%u662F%u5927%u52BF%u6240%u8D8B%u3002%u5728%u5B98%u65B9%u5BA3%u5E03%u652F%u6301%u4E4B%u524D%uFF0C%u5DF2%u7ECF%u6709%u5F88%u591A%u7B2C%u4E09%u65B9%u5E93%u505A%u4E86polyfill%uFF0C%u5C31%u662F%u5BF9%u539F%u6709%u51FD%u6570%u8FDB%u884C%u4E86%u5305%u88C5%uFF0C%u4EE5%u4F7F%u5176%u652F%u6301Promise%u3002%u4F8B%u5982%u4E0B%u9762%u7684%u5E93%uFF1A%0A-%20Q%0A-%20when%0A-%20WinJS%0A-%20RSVP.js%0A%0A%u4E0A%u9762%u8FD9%u4E9B%u5E93%u548C%20JavaScript%20%u539F%u751F%20Promise%20%u90FD%u9075%u5B88%u4E00%u4E2A%u901A%u7528%u7684%u3001%u6807%u51C6%u5316%u7684%u89C4%u8303%uFF1A%5BPromises/A+%5D%28https%3A//github.com/promises-aplus/promises-spec%29%u3002jQuery%u7684Promise-Defer%u7684%u5B9E%u73B0%u4E0D%u7B26%u5408%u8FD9%u4E2A%u6807%u51C6%uFF0C%u4F7F%u7528%u9700%u8C28%u614E%u3002%0A%0A%u53E6%u5916%u5404%u79CDPromise%u7684%u5B9E%u73B0%u867D%u6982%u5FF5%u4E00%u81F4%uFF0C%u4F46%u4ECD%u4E0D%u5C3D%u76F8%u540C%u3002%u770B%u4E86%u5F88%u591A%u6587%u7AE0%uFF0C%u4ECD%u7136%u4E91%u91CC%u96FE%u91CC%u3002%u521A%u5F00%u59CB%u63A5%u89E6%u7684%u662F%u8FD9%u4E00%u7BC7%u6587%u7AE0%uFF1A%0A-%20%5BES6%20JavaScript%20Promise%u7684%u611F%u6027%u8BA4%u77E5%5D%28https%3A//www.evernote.com/shard/s24/nl/2724128/9fc4fe1b-7fe2-443c-81cf-ab053fa387e6%29%0A%u8BB2%u7684%u6709%u70B9%u5570%u55E6%uFF0C%u6587%u672B%u7F57%u5217%u7684%u53C2%u8003%u6587%u732E%u770B%u8D77%u6765%u8FD8%u4E0D%u9519%u3002%u540E%u6765%u9646%u9646%u7EED%u7EED%u53C8google%u4E86%u5F88%u591A%u6587%u7AE0%uFF0C%u8BB2%u7684%u4E5F%u4E0D%u5C3D%u76F8%u540C%uFF0C%u7406%u89E3%u4E0D%u80FD%u3002%u518D%u540E%u6765%u770B%u5230%u4E86%u4E0B%u9762%u8FD9%u4E00%u7BC7%u6587%u7AE0%u52A0%u4E00%u672C%u5728%u7EBF%u4E66%uFF0C%u611F%u89C9%u5F88%u4E0D%u9519%u3002%0A-%20%5BJavaScript%20Promises%5D%28https%3A//www.evernote.com/shard/s24/nl/2724128/bb1e3fc0-b85b-4fcf-a370-e13ba0573d6e%29%0A-%20%5BJavaScript%20Promise%u8FF7%u4F60%u4E66%uFF08%u4E2D%u6587%u7248%uFF09%5D%28http%3A//liubin.org/promises-book/%23ch2-promise-resolve%29%0A%0A%23%23%20Promise%u7684%u672F%u8BED%0A-%20%u80AF%u5B9A%uFF08fulfilled%uFF09%20%0A%09-%20%u8BE5%20Promise%20%u5BF9%u5E94%u7684%u64CD%u4F5C%u6210%u529F%u4E86%20%0A-%20%u5426%u5B9A%uFF08rejected%uFF09%20%0A%09-%20%u8BE5%20Promise%20%u5BF9%u5E94%u7684%u64CD%u4F5C%u5931%u8D25%u4E86%20%0A-%20%u7B49%u5F85%uFF08pending%uFF09%20%0A%09-%20%u8FD8%u6CA1%u6709%u5F97%u5230%u80AF%u5B9A%u6216%u8005%u5426%u5B9A%u7ED3%u679C%uFF0C%u8FDB%u884C%u4E2D%20%0A-%20%u7ED3%u675F%uFF08settled%uFF09%20%0A%09-%20%u5DF2%u7ECF%u80AF%u5B9A%u6216%u8005%u5426%u5B9A%u4E86%0A-%20%u7C7B%20Promise%20%28thenable%29%0A%09-%20%u4E00%u4E2A%u5BF9%u8C61%u662F%u5426%u662F%u201C%u7C7B%20Promise%u201D%uFF08%u62E5%u6709%u540D%u4E3A%u201Cthen%u201D%u7684%u65B9%u6CD5%uFF09%u7684%u3002%0A%0A%u521D%u59CB%u72B6%u6001%u662Fpending%uFF0Csettled%3Dfulfilled%20or%20rejected%uFF0C%u72B6%u6001%u8F6C%u6362%u53EA%u6709%u4E00%u6B21%uFF0C%u800C%u4E14%u4E0D%u80FD%u9006%u8F6C%u3002%0A%0A%23%23%u5982%u4F55%u4F7F%u7528Promise%0A%60%60%60javascript%0Avar%20promise%20%3D%20new%20Promise%28function%28resolve%2C%20reject%29%20%7B%0A%20%20//%20%u505A%u4E00%u4E9B%u5F02%u6B65%u64CD%u4F5C%u7684%u4E8B%u60C5%uFF0C%u7136%u540E%u2026%u2026%0A%0A%20%20if%20%28/*%20%u4E00%u5207%u6B63%u5E38%20*/%29%20%7B%0A%20%20%20%20resolve%28%22Stuff%20worked%21%22%29%3B%0A%20%20%7D%0A%20%20else%20%7B%0A%20%20%20%20reject%28Error%28%22It%20broke%22%29%29%3B%0A%20%20%7D%0A%7D%29%0A%0Apromise.then%28function%28result%29%20%7B%0A%20%20console.log%28result%29%3B%20//%20%u201C%u5B8C%u7F8E%uFF01%u201D%0A%7D%2C%20function%28err%29%20%7B%0A%20%20console.log%28err%29%3B%20//%20Error%3A%20%22%u51FA%u95EE%u9898%u4E86%22%0A%7D%29%3B%3B%0A%60%60%60%0A%23%23%23%u6784%u9020%u51FD%u6570%0A%3E%u6784%u9020%u5668%u63A5%u53D7%u4E00%u4E2A%u51FD%u6570%u4F5C%u4E3A%u53C2%u6570%uFF0C%u5B83%u4F1A%u4F20%u9012%u7ED9%u8FD9%u4E2A%u56DE%u8C03%u51FD%u6570%u4E24%u4E2A%u53D8%u91CF%20resolve%20%u548C%20reject%u3002%u5728%u56DE%u8C03%u51FD%u6570%u4E2D%u505A%u4E00%u4E9B%u5F02%u6B65%u64CD%u4F5C%uFF0C%u6210%u529F%u4E4B%u540E%u8C03%u7528%20resolve%uFF0C%u5426%u5219%u8C03%u7528%20reject%u3002%0A%0A%u4E0A%u9762%u8FD9%u6BB5%u8BDD%u7684%u610F%u601D%u662F%uFF0C%0A-%20%u9996%u5148%uFF0CPromise%u5BF9%u8C61%u7528%u4E00%u4E2A%u56DE%u8C03%u51FD%u6570%u6765%u6784%u9020%u3002%0A-%20%u5176%u6B21%uFF0C%u770B%u4E0B%u9762%u8FD9%u6BB5code%0A%09-%20%u8FD9%u4E2A%u56DE%u8C03%u51FD%u6570%u6709%u4E24%u4E2A%u53C2%u6570%uFF0C%u8FD9%u4E24%u4E2A%u53C2%u6570%u7531Promise%u6765%u8D1F%u8D23%u4F20%u9012%28resolve%26%20reject%29%u3002%0A%09-%20%u56DE%u8C03%u51FD%u6570%u4E2D%u5B9A%u4E49%u4E00%u4E9B%u81EA%u5DF1%u9700%u8981%u505A%u7684%u5F02%u6B65%u64CD%u4F5C%28asyncOp%29%u3002%0A%09-%20%u5F02%u6B65%u64CD%u4F5C%u7ED3%u675F%u65F6%uFF0C%u81EA%u884C%u5224%u65AD%u6210%u529F%u5931%u8D25%uFF0C%u5931%u8D25%u8C03%u7528reject%u51FD%u6570%uFF0C%u6210%u529F%u8C03%u7528resolve%u51FD%u6570%u3002%0A%09-%20Promise%u4F7F%u7528%u65F6%u81F3%u5C11%u4E0E%u4E00%u4E2Athen%u914D%u5BF9%uFF0C%u5728then%u91CC%u9762%u505AasyncOp%u4E4B%u540E%u60F3%u505A%u7684%u4E8B%u60C5%u3002Promise%u91CC%u9762%u7684%u7ED3%u679C%u901A%u8FC7resolve%u548Creject%u4F20%u9012%u7684%u53C2%u6570%uFF08err%26res%uFF09%u4F20%u5230then%u91CC%u3002%0A%60%60%60javascript%0Afunction%20polyFill%28%29%7B%0A%20%20return%20new%20Promise%28function%20%28resolve%2C%20reject%29%7B%0A%20%20%20%20fs.asyncOp%28function%20%28err%2C%20res%29%7B%0A%20%20%20%20%20%20if%20%28err%29%20%0A%09%20%20%20%20%20%20reject%28err%29%3B%0A%20%20%20%20%20%20else%20%0A%09%20%20%20%20%20%20resolve%28res%29%3B%0A%20%20%20%20%7D%29%3B%0A%20%20%7D%29%3B%0A%7D%0ApolyFill%28%29.then%28resolveFunc%2C%20rejectFunc%29%3B%0A%60%60%60%0A%23%23%23resolve%20%26%20reject%0A%u8FD9%u4E24%u4E2A%u662FPromise%u7C7B%u7684%u6210%u5458%u51FD%u6570%uFF0C%u4F7F%u7528%u7684%u65F6%u5019%uFF0C%u4E5F%u53EF%u4EE5%u50CF%u4E0B%u9762%u8FD9%u6837%u8C03%u7528%uFF1A%0A%60%60%60javascript%0Apromise.resolve%28val%29.then%28...%29%3B%0Apromise.reject%28Error%28err%29%29.then%28...%29%3B%0A%60%60%60%0A%u8FD9%u6837%u7684%u8C03%u7528%u548C%u4E0B%u9762%u7684%u5E38%u89C4%u8C03%u7528%u662F%u7B49%u6548%u7684%uFF1A%0A%60%60%60javascript%0Avar%20promise%20%3D%20Promise%28function%28resolve%2C%20reject%29%7B%0A%09if%20%28good%29%7B%0A%09%09resolve%28val%29%3B%09%0A%09%7D%0A%09else%20%7B%0A%09%09reject%28Error%28err%29%29%3B%0A%09%7D%0A%7D%29%3B%0Apromise.then%28...%29%3B%0A%60%60%60%0A%u5404%u8DEF%u8D34%u5B50%u548C%u89E3%u8BF4%u4E2D%u90FD%u9C9C%u5C11%u63D0%u53CAresolve%u548Creject%u51FD%u6570%u7684%u53C2%u6570%u662F%u4EC0%u4E48%uFF0C%u6709%u4EC0%u4E48%u7528%u3002%u6211%u4E4B%u524D%u5F15%u7528%u7684%u4E24%u4E2A%u94FE%u63A5%u90FD%u6709%u8BF4%u5230%uFF0C%u4F46%u6211%u611F%u89C9%u8BB2%u7684%u90FD%u4E0D%u662F%u5F88%u6E05%u695A%u3002%u4E0B%u9762%u7406%u4E00%u4E0B%u6211%u7684%u601D%u8DEF%uFF1A%0A%3E%20resolve%0A%3E%20-%20resolve%u603B%u662F%u8FD4%u56DE%u4E00%u4E2APromise%u5BF9%u8C61%0A%3E%20-%20%u63A5%u6536%u5230promise%u5BF9%u8C61%u53C2%u6570%u7684%u65F6%u5019%2C%20%u8FD4%u56DE%u7684%u8FD8%u662F%u63A5%u6536%u5230%u7684promise%u5BF9%u8C61%uFF0C%u800C%u4E14%u4E0B%u4E00%u4E2Athen%u4F1A%u7B49%u5230%u8FD9%u4E2APromise%u5B9A%u4E49%u7684%u5F02%u6B65%u64CD%u4F5C%u7ED3%u675F%u65F6%u624D%u4F1A%u8FD0%u884C%u3002%0A%3E%20-%20%u63A5%u6536%u5230thenable%u7C7B%u578B%u7684%u5BF9%u8C61%u7684%u65F6%u5019%2C%20%u8FD4%u56DE%u4E00%u4E2A%u65B0%u7684promise%u5BF9%u8C61%uFF0C%u8FD9%u4E2A%u5BF9%u8C61%u5177%u6709%u4E00%u4E2A%20then%20%u65B9%u6CD5%0A%3E%20-%20%u63A5%u6536%u7684%u53C2%u6570%u4E3A%u5176%u4ED6%u7C7B%u578B%u7684%u65F6%u5019%uFF08%u5305%u62ECJavaScript%u5BF9%u6216null%u7B49%uFF09%2C%20%u8FD4%u56DE%u4E00%u4E2A%u5C06%u8BE5%u5BF9%u8C61%u4F5C%u4E3A%u503C%u7684%u65B0promise%u5BF9%u8C61%uFF0C%u8FD9%u4E2A%u503C%u4F1A%u88AB%u5F53%u4F5C%u53C2%u6570%u4F20%u5230%u4E0B%u4E00%u4E2A%u5B9A%u4E49%u4E86resolve%u51FD%u6570%u53C2%u6570%u7684then%u91CC%u9762%u3002%0A%3E%20reject%0A%3E%20%20-%20reject%u4E0Eresolve%u7C7B%u4F3C%uFF0C%u8FD4%u56DE%u7684%u4E5F%u662F%u4E00%u4E2APromise%u5BF9%u8C61%0A%3E%20%20-%20reject%u901A%u5E38%u4F20%u5165%u4E00%u4E2A%60Error%28%29%60%u5BF9%u8C61%u3002%u8FD9%u4E0D%u662F%u5FC5%u987B%u7684%uFF0C%u4F46Error%u5BF9%u8C61%u5305%u542B%u4E86%u8C03%u7528%u6808%uFF0C%u65B9%u4FBF%u8C03%u8BD5%u3002%0A%0A%23%23%23%u9519%u8BEF%u5904%u7406%0A%u4E0A%u9762%u63D0%u5230%u4E86reject%u5DF2%u7ECF%u6D89%u53CA%u5230%u4E00%u90E8%u5206%u9519%u8BEF%u5904%u7406%u4E86%u3002%u5728Promise%u91CC%u9762%uFF0C%u6211%u4EEC%u8FD8%u53EF%u4EE5%u7528catch%u3002%0A%60%60%60javascript%0Aget%28%27story.json%27%29.then%28function%28response%29%20%7B%20%0A%09console.log%28%22Success%21%22%2C%20response%29%3B%0A%7D%29.catch%28function%28error%29%20%7B%0A%09console.log%28%22Failed%21%22%2C%20error%29%3B%0A%7D%29%3B%0A%60%60%60%0A%u5728%5BJavaScript%20Promises%5D%28https%3A//www.evernote.com/shard/s24/nl/2724128/bb1e3fc0-b85b-4fcf-a370-e13ba0573d6e%29%u8FD9%u91CC%u63D0%u5230%uFF1A%0A%3E%u8FD9%u91CC%u7684%20catch%20%u5E76%u65E0%u4EFB%u4F55%u7279%u6B8A%u4E4B%u5904%uFF0C%u53EA%u662F%20then%28undefined%2C%20func%29%20%u7684%u8BED%u6CD5%u7CD6%u8863%uFF0C%u66F4%u76F4%u89C2%u4E00%u70B9%u800C%u5DF2%u3002catch%u76F8%u5F53%u4E8E%uFF1A%0A%60%60%60javascript%0Aget%28%27story.json%27%29.then%28function%28response%29%20%7B%0A%20%20console.log%28%22Success%21%22%2C%20response%29%3B%0A%7D%29.then%28undefined%2C%20function%28error%29%20%7B%0A%20%20console.log%28%22Failed%21%22%2C%20error%29%3B%0A%7D%29%3B%0A%60%60%60%0A%u603B%u7ED3%u4E00%u4E0B%uFF1A%0A%u5728Promise%u4E2D%u7684%u4EFB%u4F55%u9519%u8BEF%uFF0C%u5305%u62EC%uFF1A%0A-%20reject%u8C03%u7528%0A-%20Promise%u4E2D%u7684%u629B%u51FA%u7684%u5F02%u5E38%0A%0A%u4F1A%u843D%u5230Promise%u540E%u9762%u7B2C%u4E00%u4E2A%u5B9A%u4E49%u4E86reject%u51FD%u6570%u53C2%u6570%u7684then%u5206%u652F%u91CC%u3002%0A%0A%23%23%u9AD8%u7EA7Promise%0A%23%23%23%u521B%u5EFA%u5E8F%u5217%0A%u5728%5BJavaScript%20Promises%5D%28https%3A//www.evernote.com/shard/s24/nl/2724128/bb1e3fc0-b85b-4fcf-a370-e13ba0573d6e%29%u4E2D%uFF0C%u8BE6%u7EC6%u89E3%u91CA%u4E86%u5982%u4F55%u64CD%u4F5C%u4E00%u7EC4Promise%uFF0C%u4ECE%u771F%u6B63%u7684%u7528%u4E00%u7EC4Promise%u5230%u4F7F%u7528Promise.all%u3002%u8FD9%u91CC%u8BB2%u7684%u5F88%u597D%u4E86%uFF0C%u6211%u76F4%u63A5%u62F7%u8FC7%u6765%u3002%0A1.%20%u6700%u76F4%u63A5%u7684%u6570%u7EC4forEach%0A%60%60%60javascript%0A//%20%u4ECE%u4E00%u4E2A%u5B8C%u6210%u72B6%u6001%u7684%20Promise%20%u5F00%u59CB%0Avar%20sequence%20%3D%20Promise.resolve%28%29%3B%0A%0A//%20%u904D%u5386%u6240%u6709%u7AE0%u8282%u7684%20url%0Astory.chapterUrls.forEach%28function%28chapterUrl%29%20%7B%0A%20%20//%20%u4ECE%20sequence%20%u5F00%u59CB%u628A%u64CD%u4F5C%u63A5%u9F99%u8D77%u6765%0A%20%20sequence%20%3D%20sequence.then%28function%28%29%20%7B%0A%20%20%20%20return%20getJSON%28chapterUrl%29%3B%0A%20%20%7D%29.then%28function%28chapter%29%20%7B%0A%20%20%20%20addHtmlToPage%28chapter.html%29%3B%0A%20%20%7D%29%3B%0A%7D%29%3B%0A%60%60%60%0A2.%20%u7528%20%5Barray.reduce%5D%28https%3A//developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce%29%20%u7CBE%u7B80%u4E00%u4E0B%u4E0A%u9762%u7684%u4EE3%u7801%uFF1A%0A%60%60%60javascript%0A//%20%u904D%u5386%u6240%u6709%u7AE0%u8282%u7684%20url%0Astory.chapterUrls.reduce%28function%28sequence%2C%20chapterUrl%29%20%7B%0A%20%20//%20%u4ECE%20sequence%20%u5F00%u59CB%u628A%u64CD%u4F5C%u63A5%u9F99%u8D77%u6765%0A%20%20return%20sequence.then%28function%28%29%20%7B%0A%20%20%20%20return%20getJSON%28chapterUrl%29%3B%0A%20%20%7D%29.then%28function%28chapter%29%20%7B%0A%20%20%20%20addHtmlToPage%28chapter.html%29%3B%0A%20%20%7D%29%3B%0A%7D%2C%20Promise.resolve%28%29%29%3B%0A%60%60%60%0A3.%20%u6211%u4EEC%u5E0C%u671B%u540C%u65F6%u4E0B%u8F7D%u6240%u6709%u7AE0%u8282%uFF0C%u5168%u90E8%u5B8C%u6210%u540E%u4E00%u6B21%u641E%u5B9A%uFF0C%u6B63%u597D%u5C31%u6709%u8FD9%u4E48%u4E2A%20API%uFF1A%0A%60%60%60javascript%0APromise.all%28arrayOfPromises%29.then%28function%28arrayOfResults%29%20%7B%0A%20%20//...%0A%7D%29%3B%0A%60%60%60%0A%u7136%u800C%u8FD9%u6837%u8FD8%u662F%u6709%u63D0%u9AD8%u7A7A%u95F4%u3002%u5F53%u7B2C%u4E00%u7AE0%u5185%u5BB9%u52A0%u8F7D%u5B8C%u6BD5%u6211%u4EEC%u53EF%u4EE5%u7ACB%u5373%u586B%u8FDB%u9875%u9762%uFF0C%u8FD9%u6837%u7528%u6237%u53EF%u4EE5%u5728%u5176%u4ED6%u52A0%u8F7D%u4EFB%u52A1%u5C1A%u672A%u5B8C%u6210%u4E4B%u524D%u5C31%u5F00%u59CB%u9605%u8BFB%uFF1B%u5F53%u7B2C%u4E09%u7AE0%u5230%u8FBE%u7684%u65F6%u5019%u6211%u4EEC%u4E0D%u52A8%u58F0%u8272%uFF0C%u7B2C%u4E8C%u7AE0%u4E5F%u5230%u8FBE%u4E4B%u540E%u6211%u4EEC%u518D%u628A%u7B2C%u4E8C%u7AE0%u548C%u7B2C%u4E09%u7AE0%u5185%u5BB9%u586B%u5165%u9875%u9762%uFF0C%u4EE5%u6B64%u7C7B%u63A8%u3002%0A%0A4.%20%u4E3A%u4E86%u8FBE%u5230%u8FD9%u6837%u7684%u6548%u679C%uFF0C%u6211%u4EEC%u540C%u65F6%u8BF7%u6C42%u6240%u6709%u7684%u7AE0%u8282%u5185%u5BB9%uFF0C%u7136%u540E%u521B%u5EFA%u4E00%u4E2A%u5E8F%u5217%u4F9D%u6B21%u5C06%u5176%u586B%u5165%u9875%u9762%uFF1A%0A%60%60%60javascript%0AgetJSON%28%27story.json%27%29.then%28function%28story%29%20%7B%0A%20%20addHtmlToPage%28story.heading%29%3B%0A%0A%20%20//%20%u628A%u7AE0%u8282%20URL%20%u6570%u7EC4%u8F6C%u6362%u6210%u5BF9%u5E94%u7684%20Promise%20%u6570%u7EC4%0A%20%20//%20%u8FD9%u6837%u5C31%u53EF%u4EE5%u5E76%u884C%u52A0%u8F7D%u5B83%u4EEC%0A%20%20return%20story.chapterUrls.map%28getJSON%29%0A%20%20%20%20.reduce%28function%28sequence%2C%20chapterPromise%29%20%7B%0A%20%20%20%20%20%20//%20%u4F7F%u7528%20reduce%20%u628A%u8FD9%u4E9B%20Promise%20%u63A5%u9F99%0A%20%20%20%20%20%20//%20%u4EE5%u53CA%u5C06%u7AE0%u8282%u5185%u5BB9%u6DFB%u52A0%u5230%u9875%u9762%0A%20%20%20%20%20%20return%20sequence.then%28function%28%29%20%7B%0A%20%20%20%20%20%20%20%20//%20%u7B49%u5F85%u5F53%u524D%20sequence%20%u4E2D%u6240%u6709%u7AE0%u8282%u548C%u672C%u7AE0%u8282%u7684%u6570%u636E%u5230%u8FBE%0A%20%20%20%20%20%20%20%20return%20chapterPromise%3B%0A%20%20%20%20%20%20%7D%29.then%28function%28chapter%29%20%7B%0A%20%20%20%20%20%20%20%20addHtmlToPage%28chapter.html%29%3B%0A%20%20%20%20%20%20%7D%29%3B%0A%20%20%20%20%7D%2C%20Promise.resolve%28%29%29%3B%0A%7D%29.then%28function%28%29%20%7B%0A%20%20addTextToPage%28%22All%20done%22%29%3B%0A%7D%29.catch%28function%28err%29%20%7B%0A%20%20//%20%u6355%u83B7%u8FC7%u7A0B%u4E2D%u7684%u4EFB%u4F55%u9519%u8BEF%0A%20%20addTextToPage%28%22Argh%2C%20broken%3A%20%22%20+%20err.message%29%3B%0A%7D%29.then%28function%28%29%20%7B%0A%20%20document.querySelector%28%27.spinner%27%29.style.display%20%3D%20%27none%27%3B%0A%7D%29%3B%0A%60%60%60%0A%0A