JavaScript当前有众多实现异步编程的方式,最为耀眼的就是ECMAScript 6规范中的Promise对象,它来自于CommonJS小组的努力:Promise/A+规范。
研究javascript的异步编程,jsDeferred也是有必要探索的:因为Promise/A+规范的制定基本上是奠定在jsDeferred上,它是javascript异步编程中里程碑式的作品。jsDeferred自身的实现也是非常有意思的。
本文将探讨项目jsDeferred的模型,带我们感受一个不一样的异步编程体验和实现。
本文内容如下:
- jsDeferred和Promise/A+
- jsDeferred的工作模型
- jsDeferred API
- 参考和引用
jsDeferred和Promise/A+
在上一篇文章《JavaScript异步编程(1)- ECMAScript 6的Promise对象》中,我们讨论了ECMAScript 6的Promise对象,这一篇我们来看javascript异步编程的先驱者——jsDeferred。
jsDeferred是日本javascript高手geek cho45受MochiKit.Async.Deferred模块启发在2007年开发(07年就在玩这个了…)的一个异步执行类库。我们将jsDeferred的原型和Promise/A+规范(译文戳这里)进行对比(来自^_^肥仔John的《JS魔法堂:jsDeferred源码剖析》):
Promise/A+
- Promise是基于状态的
- 状态标识:pending(初始状态)、fulfilled(成功状态)和rejected(失败状态)。
- 状态为单方向移动“pending->fulfilled”,”pending->rejected”。
- 由于存在状态标识,所以支持晚事件处理的晚绑定。
jsDeferred
- jsDeferred是基于事件的,并没有状态标识
- 实例的成功/失败事件是基于事件触发而被调用
- 因为没有状态标识,所以可以多次触发成功/失败事件
- 不支持晚绑定
jsDeferred的工作模型
下面一张图粗略演示了jsDeferred的工作模型。
下面涉及到jsDeferred的源码,对于第一次接触的童鞋请直接拉到API一节(下一节),读完了API再来看这里。
jsDeferred第一次调用next有着不同的处理,jsDeferred在第一次调用next()的时候,会立即异步执行这个回调函数——而这个挂起异步,则视当前的环境(如浏览器最佳环境)选择最优的异步挂起方案,例如现代浏览器下会通过创建Image对象的方式来进行异步挂起,摘录源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
Deferred.next_faster_way_Image = ((typeof window === 'object') && (typeof (Image) != "undefined") && !window.opera && document.addEventListener) && function (fun) { // Modern Browsers var d = new Deferred(); var img = new Image(); var handler = function () { d.canceller(); d.call(); }; //进行异步挂起 img.addEventListener("load", handler, false); img.addEventListener("error", handler, false); d.canceller = function () { img.removeEventListener("load", handler, false); img.removeEventListener("error", handler, false); }; img.src = "data:image/png," + Math.random(); if (fun) d.callback.ok = fun; return d; }; |
Deferred对象的静态方法 – Deferred.next()源码:
1 2 3 4 5 |
Deferred.next = Deferred.next_faster_way_readystatechange ||//IE下使用onreadystatechange() Deferred.next_faster_way_Image ||//现代浏览器下使用Image对象onload/onerror事件 Deferred.next_tick ||//Node下使用process.nextTick() Deferred.next_default;//默认使用setTimeout |
我们务必要理清Deferred.next()和Deferred.prototype.next(),这是两种不同的东西:
- Deferred.next()的职责是压入异步的代码,并立即异步执行的。
- Deferred.prototype.next()是从上一个Deferred对象链中构建的Deferred。当没有上一个Deferred链的时候,它并不会执行next()中压入的函数,它的执行继承于上一个Deferred触发的事件或自身事件的触发[ call / fail ]。
摘录源码如下:
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 |
Deferred.prototype = { callback: {}, next: function (fun) {//压入一个函数并返回新的Deferred对象 return this._post("ok", fun) }, call: function (val) {//触发当前Deferred成功的事件 return this._fire("ok", val) }, _post: function (okng, fun) {//next()底层 this._next = new Deferred(); this._next.callback[okng] = fun; return this._next; }, _fire: function (okng, value) {//call()底层 |