有时候,你会发现满世界都是某个字眼。

Promise,就是这个家伙。简直阴魂不散,走到哪儿都能看到它。fetchkoa中都有TA的身影,而我却对TA一无所知。
搜索TA的文章介绍,第一页中最早的一篇文章已经是2011年了。那是个我还不会HelloWorld的年代啊!

用法

Promise 对象用于延迟(deferred) 计算和异步(asynchronous ) 计算。一个Promise对象代表着一个还未完成,但预期将来会完成的操作。

看概念很迷糊,直接看实例理解吧。

function $http(url){
var core = {
ajax : function (method, url, args) {
var promise = new Promise( function (resolve, reject) {
var client = new XMLHttpRequest();
var uri = url;
if (args && (method === 'POST' || method === 'PUT')) {
uri += '?';
var argcount = 0;
for (var key in args) {
if (args.hasOwnProperty(key)) {
if (argcount++) {
uri += '&';
}
uri += encodeURIComponent(key) + '=' + encodeURIComponent(args[key]);
}
}
}
client.open(method, uri);
client.send();
client.onload = function () {
if (this.status >= 200 && this.status < 300) {
resolve(this.response);
} else {
reject(this.statusText);
}
};
client.onerror = function () {
reject(this.statusText);
};
});
return promise;
}
};
return {
get : function(args) {
return core.ajax('GET', url, args);
},
post : function(args) {
return core.ajax('POST', url, args);
},
put : function(args) {
return core.ajax('PUT', url, args);
},
delete : function(args) {
return core.ajax('DELETE', url, args);
}
};
};
$http('http://url/api').get({
id: 111
}).then(function(data){
// success
}, function(data){
// error
});

以上是一个基于 Promise 的 ajax 实现,调用形式跟 vue-resource 如出一辙(果然又是大神们嚼烂的玩意儿)。

Promise 的构造函数可以带有1个参数,它是带有resolvereject两个参数的函数对象,如以上core.ajax函数中创建的 Promise 对象。
其中 resolve 用于处理执行成功的场景,reject 用于处理执行失败的场景。
在成功与失败的处理阶段 hook 到一个 Promise 对象中,最后通过then方法来真正的处理返回结果。

  • Promise.prototype.then(onFulfilled[, onRejected])
  • Promise.prototype.catch(onRejected)

并不是所有的失败场景都需要在then方法中处理,在其后继续追加catch方法也是可以的。

$http('http://url/api').get({
id: 111
}).then(function(data){
// success
}).catch(function(data){
// error
});

由于thencatch仍然返回一个 Promise 对象,所以可以出现多个thencatch来处理不同的业务场景。

$http('http://url/api').get({
id: 111
}).then(function(data){
return handleA(data);
}).then(function(data){
// 此处 data 为 handleA 方法处理后的数据
return handleB(data);
});

为何要用

咋一看 Promise 就是一个包装好的代理对象。不过它的出现究竟是为了解决什么问题呢?

在 Promise 出现之前,如果你的逻辑中出现了异步操作,而又需要在异步操作外获取异步操作内的结果,不外乎是要使用回调了。

function getList(callback) {
$.get(yourApi, function(data){
callback(data);
});
}

使用 Promise 呢?

function getList() {
return new Promise( function (resolve, reject) {
$.get(yourApi, function(data){
resolve(data);
}).error(function(data){
reject(data);
});
}
}

使用 Promise 代理了原来所需要的回调的,返回一个对象而不是传入回调参数的方法形式更直观和容易理解。
在之后的编程中,当目标方法返回了一个 Promise 对象,我们就知道发生了异步操作,需要通过 then 方法来处理场景需求。

到此为止,Promise 给我的感觉也只不过是语法糖罢了,这种形式的写法我也可以封装出来啊。
接下来才是其真正的优点。

比如异步处理多个的请求结果,原始写法。

function getData(callback){
$.get('/ApiA', function(dataA){
$.get('/ApiB', function(dataB){
$.get('/ApiC', function(dataC){
callback(dataA, dataB, dataC);
});
});
});
}

当然以上是一种比较蠢的写法,当请求数量发生变化时的时候,这段代码就需要修改了。
可以用递归优化下。

function getData(url, callback){
if(url instanceof Array) {
var i = 0,
len = url.length,
dataArr = [],
eachLoad = function(url){
$.get(url, function(data){
dataArr.push(data);
if(i++ < len - 1) {
eachLoad(url[i]);
} else {
callback.apply(null, dataArr);
}
});
};
eachLoad(url[i]);
} else {
$.get(url, function(data){
callback(data);
});
}
}

使用 Promise 解决。

var promiseA = $http('/List').get({
id: 111
}),
promiseB = $http('/List').get({
id: 222
});
Promise.all([promiseA, promiseB]).then(function(data){
// do...
});

使用Promise.all方法一次性处理多个操作。

Promise 属于 ES6 特性,目前在 IE 中还不能用,不过各大框架也都有自己的实现。

jQuery 中的 Promise

jQuery 中很多异步操作都会返回一个延迟对象$.Deferred,该对象具有一系列的操作方法。

// animate
$('div').animate({
width: '50%'
}, 300).done(function(){
// 当延迟成功时调用
});
// ajax
$.get('/List').then(function(){
// succee
}, function(){
// error
});
// when
$.when($.ajax('/List1'), $.ajax('/List2')).then(function(){
// succee
}, function(){
// error
});
// 还有很多,可以去 jQuery Api 中看。

参考