2.7. Promise和数组

到目前为止我们已经学习了如何通过 .then.catch 来注册回调函数,这些回调函数会在promise对象变为 FulFilled 或 Rejected 状态之后被调用。

如果只有一个promise对象的话我们可以像前面介绍的那样编写代码就可以了,如果要在多个promise对象都变为FulFilled状态的时候才要进行某种处理话该如何操作呢?

我们以当所有XHR(异步处理)全部结束后要进行某操作为例来进行说明。

各位读者现在也许有点难以在大脑中描绘出这么一种场景,我们可以先看一下下面使用了普通的回调函数风格的XHR处理代码。

2.7.1. 通过回调方式来进行多个异步调用

multiple-xhr-callback.js

  1. function getURLCallback(URL, callback) {
  2. var req = new XMLHttpRequest();
  3. req.open('GET', URL, true);
  4. req.onload = function () {
  5. if (req.status === 200) {
  6. callback(null, req.responseText);
  7. } else {
  8. callback(new Error(req.statusText), req.response);
  9. }
  10. };
  11. req.onerror = function () {
  12. callback(new Error(req.statusText));
  13. };
  14. req.send();
  15. }
  16. // <1> 对JSON数据进行安全的解析
  17. function jsonParse(callback, error, value) {
  18. if (error) {
  19. callback(error, value);
  20. } else {
  21. try {
  22. var result = JSON.parse(value);
  23. callback(null, result);
  24. } catch (e) {
  25. callback(e, value);
  26. }
  27. }
  28. }
  29. // <2> 发送XHR请求
  30. var request = {
  31. comment: function getComment(callback) {
  32. return getURLCallback('http://azu.github.io/promises-book/json/comment.json', jsonParse.bind(null, callback));
  33. },
  34. people: function getPeople(callback) {
  35. return getURLCallback('http://azu.github.io/promises-book/json/people.json', jsonParse.bind(null, callback));
  36. }
  37. };
  38. // <3> 启动多个XHR请求,当所有请求返回时调用callback
  39. function allRequest(requests, callback, results) {
  40. if (requests.length === 0) {
  41. return callback(null, results);
  42. }
  43. var req = requests.shift();
  44. req(function (error, value) {
  45. if (error) {
  46. callback(error, value);
  47. } else {
  48. results.push(value);
  49. allRequest(requests, callback, results);
  50. }
  51. });
  52. }
  53. function main(callback) {
  54. allRequest([request.comment, request.people], callback, []);
  55. }
  56. // 运行的例子
  57. main(function(error, results){
  58. if(error){
  59. return console.error(error);
  60. }
  61. console.log(results);
  62. });

这段回调函数风格的代码有以下几个要点。

  • 直接使用 JSON.parse 函数的话可能会抛出异常,所以这里使用了一个包装函数 jsonParse

  • 如果将多个XHR处理进行嵌套调用的话层次会比较深,所以使用了 allRequest 函数并在其中对request进行调用。

  • 回调函数采用了 callback(error,value) 这种写法,第一个参数表示错误信息,第二个参数为返回值

在使用 jsonParse 函数的时候我们使用了 bind 进行绑定,通过使用这种偏函数(Partial Function)的方式就可以减少匿名函数的使用。(如果在函数回调风格的代码能很好的做到函数分离的话,也能减少匿名函数的数量)

  1. jsonParse.bind(null, callback);
  2. // 可以认为这种写法能转换为以下的写法
  3. function bindJSONParse(error, value){
  4. jsonParse(callback, error, value);
  5. }

在这段回调风格的代码中,我们也能发现如下一些问题。

  • 需要显示进行异常处理

  • 为了不让嵌套层次太深,需要一个对request进行处理的函数

  • 到处都是回调函数

下面我们再来看看如何使用 Promise#then 来完成同样的工作。

2.7.2. 使用Promise#then同时处理多个异步请求

需要事先说明的是 Promise.all 比较适合这种应用场景的需求,因此我们故意采用了大量 .then 的晦涩的写法。

使用了.then 的话,也并不是说能和回调风格完全一致,大概重写后代码如下所示。

multiple-xhr.js

  1. function getURL(URL) {
  2. return new Promise(function (resolve, reject) {
  3. var req = new XMLHttpRequest();
  4. req.open('GET', URL, true);
  5. req.onload = function () {
  6. if (req.status === 200) {
  7. resolve(req.responseText);
  8. } else {
  9. reject(new Error(req.statusText));
  10. }
  11. };
  12. req.onerror = function () {
  13. reject(new Error(req.statusText));
  14. };
  15. req.send();
  16. });
  17. }
  18. var request = {
  19. comment: function getComment() {
  20. return getURL('http://azu.github.io/promises-book/json/comment.json').then(JSON.parse);
  21. },
  22. people: function getPeople() {
  23. return getURL('http://azu.github.io/promises-book/json/people.json').then(JSON.parse);
  24. }
  25. };
  26. function main() {
  27. function recordValue(results, value) {
  28. results.push(value);
  29. return results;
  30. }
  31. // [] 用来保存初始化的值
  32. var pushValue = recordValue.bind(null, []);
  33. return request.comment().then(pushValue).then(request.people).then(pushValue);
  34. }
  35. // 运行的例子
  36. main().then(function (value) {
  37. console.log(value);
  38. }).catch(function(error){
  39. console.error(error);
  40. });

将上述代码和回调函数风格相比,我们可以得到如下结论。

  • 可以直接使用 JSON.parse 函数

  • 函数 main() 返回promise对象

  • 错误处理的地方直接对返回的promise对象进行处理

向前面我们说的那样,main的 then 部分有点晦涩难懂。

为了应对这种需要对多个异步调用进行统一处理的场景,Promise准备了 Promise.allPromise.race 这两个静态方法。

在下面的小节中我们将对这两个函数进行说明。