异步在JavaScript中最为常见,用最简单的回调函数callback就解决问题。但是往往会陷入Callback Hell的困境之中。

Callback Hell

以最常见的AJAX GET为例:

1
2
3
4
5
6
7
8
var makeAjaxGET = function(url, callback) {
  var req = new XMLHttpRequest();
  // do something
  // callback result
};
makeAjaxGET('http://api.example.com', function(result) {
  // handle result
});

这样看上去还好,但是如果想要继续嵌套请求呢?

1
2
3
4
5
6
7
makeAjaxGET('http://api.example.com', function(result) {
  makeAjaxGET('http://api.example.com' + result.id, function(result) {
    makeAjaxGET('http://api.example.com' + result.newId, function(result) {
      // handle result
    });
  });
});

光是那一排排的括号都看的人头晕了!

Promise

此时就需要Promise出场解决问题了。

Promise是抽象异步处理对象以及对其进行各种操作的组件。

一个Promise对象有三种状态:Pending, Fullfilled, Rejected,对应未完成,成功,失败三种情况。

创建Promise对象的方法很简单:

  1. 使用new Promise(fn)返回一个新的Promise对象
  2. fn函数中处理异步结果:成功 -> 调用resolve(result);失败 -> 调用reject(reason / new Error)

成功获取Promise对象后,用then对结果进行处理即可 (Thenable)

Example

还是以AJAX GET为例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function ajaxGET(url) {
  return new Promise(function(resolve, reject) {
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function(e) {
      if (xhr.readyState !== 4) {
        return;
      }
      if (xhr.status < 400) {
        resolve(xhr.response);
      } else {
        reject(new Error('Error: ' + xhr.statusText));
      }
    };
    xhr.open('GET', url);
    xhr.send(null);
  });
}

ajaxGET('http://reqr.es/api/users?page=2')
  .then(JSON.parse)
  .then(function(result) {
    console.log(result);
  }).catch(function(error) {
    // Handle Error
    console.log(error);
  });

经过Promise封装后的AJAX调用过程清晰明了。此外利用Promise Chain特性也可以做到共享AJAX的response

Future

Promise规范已成为ECMAScript6的一部分,和Generators共同解决JavaScript异步编程的问题。最新版的Chrome(43)和Firefox(38)都可直接使用Promise,不支持的浏览器还可使用各种Promise实现类库:Qbluebird ……

许多常用的类库都用到了Promise技术,例如jQueryAngularJS

More: