Javascript最令人称道的就是它的事件回调模式,因此即使只有一个执行线程,它也能支持异步高并发。不过回调写多了,代码就会非常复杂难懂,因为回调不像同步代码,执行顺序是从上至下,读回调代码必须跳来跳去,思考什么情况下到底跳到了什么地方,很辛苦,这种情况俗称”Callback Hell”回调地狱。Promise模式是一种异步编程模式,很早就有,随着ES6的正式发布,Promise成为了Javascript原生支持的对象。因此就顺便学习了下Promise对象的使用,体会下它的作用。

现在的jQuery AJAX请求函数都支持了Promise模式,不过在之前,AJAX请求的形式如下:

$.ajax({
  url: url,
  type: 'GET',  // or POST
  dataType: 'json',
  data: data,
  success: successFunction,
  error: errorFunction
});

我们就以此方式依次请求三个地址,然后将响应中的内容取出并拼接起来。假设响应体的JSON格式是{'content': 'text'},JS代码如下:

$.ajax({
  url: '1.json',
  type: 'GET',
  dataType: 'json',
  success: function(data1) {
    $.ajax({
      url: '2.json',
      type: 'GET',
      dataType: 'json',
      success: function(data2) {
        $.ajax({
          url: '3.json',
          type: 'GET',
          dataType: 'json',
          success: function(data3) {
            content.innerHTML = data1.content + data2.content + data3.content;
          }
        });
      }
    });
  }
});

是不是觉得嵌套很多?如果连续请求更多的地址,那嵌套就要看晕了。换成Promise模式是怎样呢?我们先要写个Promise对象:

function request(url, content) {
  return new Promise(function(resolve, reject) {
    $.ajax({
      url: url,
      type: 'GET',
      dataType: 'json',
      success: function(data) {
        // Call resolve when succeed
        resolve(content + data.content);
      },
      error: function(error) {
        console.log('Error: ' + error.status + ', ' + error.statusText);
        // Call reject when failed
        reject(error);
      }
    });
  });
}

上例中,request()函数会返回一个”Promise”对象,初始化该对象时会传入一个函数,这个函数又接受两个回调函数作为参数,分别是resolvereject。顾名思义,resolve就是你操作成功时该调用的方法,resolve就是失败时该调用的。这两个回调函数都可以接受一个参数。

接下来,让我们同样实现前面依次请求三个地址的功能:

request('1.json', '')
.then(function(message) {
  return request('2.json', message);
})
.then(function(message) {
  return request('3.json', message);
})
.then(function(message) {
  var content = document.getElementById('content');
  content.innerHTML = message;
});

大家是不是发现,本来长长的嵌套,变成了顺序执行的代码了?Promise对象的then()方法可以接受两个参数,第一个参数即是我们定义Promise对象时的resolve函数,也就是异步代码执行成功后会调用的方法;第二个参数即是reject函数,也就是异步失败后会调用的方法。上例中,我们只传入了一个函数作为参数,也就是没定义reject方法。这时候,异步失败不会做任何操作。另外,我们在then()方法中又返回了Promise对象,所以可以继续then()下去。我们把上面的例子换成Lambda的方式,看上去就更简洁了:

request('1.json', '')
.then((message) => { return request('2.json', message); })
.then((message) => { return request('3.json', message); })
.then((message) => {
  var content = document.getElementById('content');
  content.innerHTML = message;
});

程序可读性是不是提高了很多?另外,如果你要将内容传到下一个then()方法,也不一定非要返回一个Promise对象,你可以返回任一其它类型的对象。返回Promise对象的话,后续then()中定义的回调函数会变成Promise对象的resolvereject函数。如果你返回其他对象,则该对象会成为后续then()中回调函数的参数。比如下面的例子:

request('1.json', '')
.then((message) => { return message + ' foo'; })
.then((message) => { return message + ' bar'; })
.then((message) => {
  var content = document.getElementById('content');
  content.innerHTML = message;
});

then()中返回的字符串,会成为下一个then()方法中回调函数的参数。因此上面的代码可以实现message的累加。

另注:现在jQuery的AJAX调用已经采用Promise模式了,所以上面的例子实际上是用不上的,主要是为了演示给大家Promise的价值所在,现在jQuery的AJAX请求形式如下:

$.ajax({
    url: '/path/to/file',
    type: 'default GET (Other values: POST)',
    dataType: 'default: Intelligent Guess (Other values: xml, json, script, or html)',
    data: {param1: 'value1'},
})
.done(function() {
    console.log("success");
})
.fail(function() {
    console.log("error");
})
.always(function() {
    console.log("complete");
});

是不是有点像Java代码中的”try … catch … finally”?

关于Promise一些更深层次的理论这里就不探讨了,网上资料很多,大家可以去搜索下。本文中的示例代码可以在这里下载