返回介绍

创建序列

发布于 2025-04-26 18:09:29 字数 4651 浏览 0 评论 0 收藏

我们需要把章节 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 新功能相结合时,它们将变得更加高效。

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。