- 内容简介
- 译者序
- 前言
- 第 1 章 安装配置新项目
- 第 2 章 Flexbox 布局介绍
- 第 3 章 用 React Native 开发一个应用
- 第 4 章 在 React Native 中使用导航
- 第 5 章 动画和滑动菜单
- 第 6 章 用 React Native 绘制 Canvas
- 第 7 章 使用 React Native 播放音频
- 第 8 章 你的第一个自定义视图
- 第 9 章 Flux 介绍
- 第 10 章 处理复杂的应用程序状态
- 第 11 章 使用 Node 来实现服务端 API
- 第 12 章 在 React Native 中使用文件上传
- 第 13 章 理解 JavaScript Promise
- 第 14 章 fetch 简介
- 第 15 章 在 iOS 中使用 SQLite
- 第 16 章 集成 Google Admob
- 第 17 章 React Native 组件国际化
- 附录 A React.js 快速介绍
- 附录 B Objective-C Primer
- 附录 C webpack 入门
创建序列
我们需要把章节 URL 数组转换成 Promise 序列。
// 开始先创建一个完成状态的 Promsie var sequence = Promise.resolve(); // 循环遍历章节 URL 数组 story.chapterUrls.forEach(function(chapterUrl) { // 在 sequence 后添加如下的操作
sequence = sequence.then(function() { return getJSON(chapterUrl); }).then(function(chapter) { addHtmlToPage(chapter.html); }); });
这是我们第一次使用 Promise.resolve 方法,它会根据你传入的参数值来返回一个 Promise。如果你传入的是 Promise 实例,它会直接将其返回(注意:有些实现可能不会按照这种方式执行,会有区别)。如果你传入的是类 Promise 对象(具有“then”方法),它会返回一个具有同样 resolve/reject 回调的 Promise。如果你传入其他任何值,如 Promise.resolve('Hello'),它会返回一个以参数值为 resolve 结果的 Promise。如果你像上面那样不传入任何值,那么它会以“undefined”作为 resolve 结果。
这里还有一个与之对应的 Promise.reject(val) 方法,它会返回一个根据你传入的参数值(或 undefined)为 reject 结果的 Promise。
我们可以使用 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());
这段代码跟前面的例子实现的功能一样,但是它不需要单独去声明一个“sequence”变量。reduce 的回调函数会在数组中的每个元素上执行。第一次执行中参数“sequence”的值为 Promise.resolve(),之后的调用过程中“sequence”的值为上次回调函数的返回值。array.reduce 在归并数组值方面非常有用,这个例子里就是用它来归并结果为一个 Promise。
我们来将所有的代码汇总如下:
getJSON('story.json').then(function(story) { addHtmlToPage(story.heading); return story.chapterUrls.reduce(function(sequence, chapterUrl) {
// 当前一个章节的 Promise 完成后 return sequence.then(function() { // 接着获取下一章节 return getJSON(chapterUrl); }).then(function(chapter) { // 添加显示到页面上 addHtmlToPage(chapter.html); }); }, Promise.resolve()); }).then(function() { // 全部完成了! addTextToPage("All done"); }).catch(function(err) { // 如果发生错误,捕获错误并输出错误信息 addTextToPage("Argh, broken: " + err.message); }).then(function() { // 总是要将 spinner 隐藏 document.querySelector('.spinner').style.display = 'none'; });
到此为止,我们已经将前面的同步代码改造成了异步版本。但是,我们还想做得更好。现在页面加载的效果如下:
其实浏览器可以同时加载多个文件,所以上文中这种依次加载章节内容的做法会降低效率。我们的期望是,章节内容可以同时加载,当所有内容加载完毕后再执行后续处理。幸运的是,有一个 API 可以帮助我们轻松实现:
Promise.all(arrayOfPromises).then(function(arrayOfResults) { //... });
Promise.all 接受一个 Promise 数组作为参数,创建一个当所有的 Promise 都 resolve 后才 resolve 的 Promise 对象,resolve 结果是一个数组,包含了前面参数数组中所有 Promise 的 resolve 结果,并且顺序和传入时的顺序是完全一致的。
getJSON('story.json').then(function(story) { addHtmlToPage(story.heading); // 接受一个 Promise 数组,等待它们全部完成 return Promise.all( // 遍历章节 url 数组并生成 Promise 数组 story.chapterUrls.map(getJSON) ); }).then(function(chapters) { // 现在我们得到了顺序的章节 JSON,遍历它们 chapters.forEach(function(chapter) { // 添加到页面中 addHtmlToPage(chapter.html); }); addTextToPage("All done"); }).catch(function(err) { // 捕获操作过程中发生的任何错误 addTextToPage("Argh, broken: " + err.message); }).then(function() { document.querySelector('.spinner').style.display = 'none'; });
根据网络状况的不同,这种方式会比顺序加载的方式快几秒,而且代码量也更少。章节内容加载完成的顺序不确定,但是显示到页面上的顺序是符合预期的。
然而,代码仍然有提高空间。当第一章内容加载完时应该尽可能早的显示到页面上,这样用户可以在其他章节内容还未加载完毕时就可以开始阅读。第三章内容到来时我们不能马上添加到页面,只能等到第二章内容也加载完毕后才能将两章内容一起添加到页面,这样才能保证用户良好的阅读体验。
为了达到这样的效果,我们需要同时请求全部章节内容,然后创建一个序列,依次将内容显示到页面上:
getJSON('story.json').then(function(story) { addHtmlToPage(story.heading); // 把章节 url 数组转换成对应获取 JSON 数据的 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'; });
到此为止,全部完成,鱼与熊掌兼得!加载所有内容的时间没有变,但是用户可以更早看到前面章节的内容了。
在示例中,各个章节内容的加载差不多同时完成,但是在处理章节内容非常多的情况时,上面逐章加载的策略就会具有显著的优势。
如果使用 Node.js 风格的回调或事件机制来实现上面的功能,那么代码量估计要翻一倍,更重要的是可读性也会下降不少。而且,Promise 厉害的地方还远不止于此,当同其他 ES6 新功能相结合时,它们将变得更加高效。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论