when.js很小,压缩后只有数kb,gzip后的大小几乎可以忽略。在Node和浏览器环境里都可以使用when.js

首先,我们看一小段代码:

var getData = function(callback) {
$.getJSON(api, function(data){
callback(data[0]);
});
} var getImg = function(src, callback) {
var img = new Image(); img.onload = function() {
callback(img);
}; img.src = src;
} var showImg = function(img) {
$(img).appendTo($('#container'));
} getData(function(data) {
getImg(data, function(img) {
showImg(img);
});
});
  • 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

这段代码完成了三个任务:1)获取数据;2)加载图片;3)显示图片,其中,任务1和2是异步,3是同步,使用的是最常见的callback机制来处理异步逻辑,好处是浅显易懂,缺点是强耦合、不直观、处理异常麻烦等等。

我们尝试用when.js改写下这段代码:

var getData = function() {
var deferred = when.defer(); $.getJSON(api, function(data){
deferred.resolve(data[0]);
}); return deferred.promise;
} var getImg = function(src) {
var deferred = when.defer(); var img = new Image(); img.onload = function() {
deferred.resolve(img);
}; img.src = src; return deferred.promise;
} var showImg = function(img) {
$(img).appendTo($('#container'));
} getData()
.then(getImg)
.then(showImg);
  • 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
  • 27
  • 28
  • 29
  • 30
  • 31

看最后三行代码,是不是一目了然,非常的语义化?来看下改写后的任务1、2多了些什么:

var deferred = when.defer();
  • 1

定义了一个deferred对象。

deferred.resolve(data);
  • 1

在异步获取数据完成时,把数据作为参数,调用deferred对象的resolve方法。

return deferred.promise;
  • 1

返回了deferred对象的promise属性。 
在Promises/A规范中,每个任务都有三种状态:默认(pending)、完成(fulfilled)、失败(rejected)。

  • 默认状态可以单向转移到完成状态,这个过程叫resolve,对应的方法是deferred.resolve(promiseOrValue);
  • 默认状态还可以单向转移到失败状态,这个过程叫reject,对应的方法是deferred.reject(reason);
  • 默认状态时,还可以通过deferred.notify(update)来宣告任务执行信息,如执行进度;
  • 状态的转移是一次性的,一旦任务由初始的pending转为其他状态,就会进入到下一个任务的执行过程中。

有人可能会觉得奇怪:改变任务状态的resolve和reject方法是定义在deferred对象上,但最后返回的却是deferred的promise属性。这么做一是因为规范就是这么定的,二是可以防止任务状态被外部改变。 
then有三个参数,分别是onFulfilled、onRejected、onProgress,通过这三个参数,就可以指定上一个任务在resolve、reject和notify时该如何处理。例如上一个任务被resolve(data),onFulfilled函数就会被触发,data作为它的参数;被reject(reason),那么onRejected就会被触发,收到reason。任何时候,onFulfilled和onRejected都只有其一可以被触发,并且只触发一次;onProgress顾名思义,每次notify时都会被调用。下面是reject和notify的用法:

function run() {
var deferred = when.defer();
var start = 1, end = 100; (function() {
if(start <= end) {
deferred.notify(start++);
setTimeout(arguments.callee, 50);
} else {
deferred.reject('time out!');
}
})(); return deferred.promise;
} run().then(undefined,
function(reason) {
alert(reason);
}, function(s) {
document.getElementById('output').innerHTML = s + '%';
}
);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

then会传递错误,也就是说有多个任务串行执行时,我们可以只在最后一个then定义onRejected。只定义了onRejected的then等同于otherwise,也就是说 otherwise(onRejected) 是 then(undefined, onRejected) 的简便写法。 
then会在try..catch..的包裹之下执行任务,所以任务的异常都会被when.js捕获,当做失败状态处理,类似这样:

try {
...
} catch (e) {
deferred.reject(e);
}
  • 1
  • 2
  • 3
  • 4
  • 5

在任务状态改变之后再then,依然可以正常工作,后续任务会立刻执行。如果要在多个任务最后做cleanup工作,而不管之前的任务成功与否,可以用ensure方法。它只接受一个参数onFulfilledOrRejected,始终会执行。另外when.js还有一个always方法,即将废弃,建议大家不要使用。 
回到上面加载图片的场景,如果把任务2变为:加载多张图片,全部完成后再执行任务3。这时候需要用到when.all,when.all接受一个promise数组,返回promise,这个promise会在promise数组中每一个promise都resolve之后再resolve。说起来拗口,看代码就明白了:

var getData = function() {
var deferred = when.defer(); $.getJSON(api, function(data){
var data = data.slice(0, 3);
deferred.resolve(data);
}); return deferred.promise;
} var getImg = function(src) {
var deferred = when.defer(); var img = new Image(); img.onload = function() {
deferred.resolve(img);
}; img.src = src; return deferred.promise;
} var showImgs = function(imgs) {
$(imgs).appendTo($('#container'));
} var getImgs = function(data) {
var deferreds = [];
for(var i = 0; i < data.length; i++) {
deferreds.push(getImg(data[i]));
}
return deferreds;
} when.all(getData().then(getImgs)).then(showImgs);
  • 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
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

如果我们只是想把一个promise数组挨个执行一遍,可以用when.settle:

var promise1 = function() {
var deferred = when.defer();
setTimeout(function() {
deferred.reject('A');
}, 2000);
return deferred.promise;
}; var promise2 = function() {
var deferred = when.defer();
setTimeout(function() {
deferred.resolve('B');
}, 2000);
return deferred.promise;
}; when.settle([promise1(), promise2()]).then(function(result) {
console.log(result); /*
[{"state":"rejected","reason":"A"},
{"state":"fulfilled","value":"B"}] */
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

有时候,我们需要引入任务竞争机制,例如从一批cdn中找到最快的那个,when.any就派上用场了,when.any接受promise数组,在其中任何一个resolve后就接着执行后续任务了。如果要在一批promise中某几个resolve后执行后续任务,可以用when.some,它比when.any多一个howMany的参数。 
Promise给异步编程代码带来了巨大的方便,从此我们可以更专注单个任务的实现,promise会很好的替我们解决任务调度问题。when.js提供的功能远远不止本文提到的这些,有兴趣的同学可以前往官方api文档了解更多。

github:https://github.com/cujojs/when#legacy-environments 
via:Jerry Qu 
本文链接:https://imququ.com/post/promises-when-js.html

最新文章

  1. 任务型sql
  2. 【总结】总结写了3个React页面后遇到的各种坑
  3. 【学习】JAVA的第一天(补)
  4. 设置某个ip对mysql服务器有权限,以及mysql定时备份
  5. 安装make命令
  6. mysql sql 百万级数据库优化方案
  7. poj3468A Simple Problem with Integers(线段树,在段更新时要注意)
  8. GIS中相交的定义(OGC相交的定义)
  9. Python之路,Day21 - 常用算法学习
  10. 将string当字节流使
  11. ReSharper C++计划上市
  12. Vim的基本使用(二)
  13. ASP.NET Core WebApi 返回统一格式参数
  14. Large-Margin Softmax Loss for Convolutional Neural Networks
  15. BOM—浏览器对象模型(Browser Object Model)
  16. Map和Collection
  17. Django:视图views(一)
  18. C++中类的多继承
  19. iOS开发手记-iOS8中使用定位服务解决方案
  20. Ubuntu 16.04 LAMP server tutorial with Apache 2.4, PHP 7 and MariaDB (instead of MySQL)

热门文章

  1. 安装linux各种桌面环境
  2. C++ pair(对组)用法(转)
  3. 吃CPU的openmp 程序
  4. asp.net mvc5 使用百度ueditor 本编辑器完整示例(上)
  5. asp.net mvc 学习资料
  6. Silverlight 后台利用代码触发 Button 的Click事件
  7. CodeForces 722A Broken Clock (水题)
  8. 在Visual studio 中解除 TFS 的账号绑定
  9. 【笔记】对自定义异常的理解(Java)
  10. AutoCAD2012启动错误 1308 源文件未找到