探讨下Javascript中的Promise模式
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”对象,初始化该对象时会传入一个函数,这个函数又接受两个回调函数作为参数,分别是resolve
和reject
。顾名思义,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对象的resolve
和reject
函数。如果你返回其他对象,则该对象会成为后续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一些更深层次的理论这里就不探讨了,网上资料很多,大家可以去搜索下。本文中的示例代码可以在这里下载。