JavaScript当前有众多实现异步编程的方式,最为耀眼的就是ECMAScript 6规范中的Promise对象,它来自于CommonJS小组的努力:Promise/A+规范。 javascript
研究javascript的异步编程,jsDeferred也是有必要探索的:由于Promise/A+规范的制定基本上是奠基在jsDeferred上,它是javascript异步编程中里程碑式的做品。jsDeferred自身的实现也是很是有意思的。html
本文将探讨项目jsDeferred的模型,带咱们感觉一个不同的异步编程体验和实现。java
本文内容以下:git
- jsDeferred和Promise/A+
- jsDeferred的工做模型
- jsDeferred API
- 参考和引用
在上一篇文章《JavaScript异步编程(1)- ECMAScript 6的Promise对象》中,咱们讨论了ECMAScript 6的Promise对象,这一篇咱们来看javascript异步编程的先驱者——jsDeferred。github
jsDeferred是日本javascript高手geek cho45受MochiKit.Async.Deferred模块启发在2007年开发(07年就在玩这个了...)的一个异步执行类库。咱们将jsDeferred的原型和Promise/A+规范(译文戳这里)进行对比(来自^_^肥仔John的《JS魔法堂:jsDeferred源码剖析》):ajax
- Promise是基于状态的
- 状态标识:pending(初始状态)、fulfilled(成功状态)和rejected(失败状态)。
- 状态为单方向移动“pending->fulfilled","pending->rejected"。
- 因为存在状态标识,因此支持晚事件处理的晚绑定。
- jsDeferred是基于事件的,并无状态标识
- 实例的成功/失败事件是基于事件触发而被调用
- 由于没有状态标识,因此能够屡次触发成功/失败事件
- 不支持晚绑定
下面一张图粗略演示了jsDeferred的工做模型。编程
下面涉及到jsDeferred的源码,对于第一次接触的童鞋请直接拉到API一节(下一节),读完了API再来看这里。segmentfault
jsDeferred第一次调用next有着不一样的处理,jsDeferred在第一次调用next()的时候,会当即异步执行这个回调函数——而这个挂起异步,则视当前的环境(如浏览器最佳环境)选择最优的异步挂起方案,例如现代浏览器下会经过建立Image对象的方式来进行异步挂起,摘录源码以下:api
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()源码:数组
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.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()底层 var next = "ok"; try { //调用deferred对象相应的事件处理函数 value = this.callback[okng].call(this, value); } catch (e) { //抛出异常则进入fail() next = "ng"; value = e; if (Deferred.onerror) Deferred.onerror(e); } if (Deferred.isDeferred(value)) { //在这里,和_post()呼应,调用Deferred链的下一个Deferred对象 value._next = this._next; } else { if (this._next) this._next._fire(next, value); } return this; } }
再一次强调,务必搞清楚Deferred.next()和Deferred.prototype.next()。
当我第一次知道jsDeferred API有一坨的时候,其实我是,是拒绝的。我跟你讲,我拒绝,由于其实我以为这根本要不了一坨,但正妹跟我讲,jsDeferred内部会加特技,是假的一坨,是表面看起来一坨。加了特技以后,jsDeferred duang~duang~duang~,很酷,很炫,很酷炫。
jsDeferred的API众多,由于jsDeferred把全部的异步问题都划分到了最小的粒子,这些API相互进行组合则能够完成逆天的异步能力,在后续的API示例中能够看到jsDeferred API组合从而完成强大的异步编程。咱们在阅读jsDeferred的API的时候应该时刻思考若是使用ES6的Promise对象又该如何去处理,阅读应该是大脑的盛宴。
貌似没有看到过jsDeferred的详细的中文API文档(原API文档),就这里顺便整理一份简单的出来(虽然它的API已经足够通俗易懂了)。值得一提的是官网的API引导例子很是的生动和实用:
Deferred()/new Deferred ()
构造函数(constructor),建立一个Deferred对象。
var defer = Deferred();//或new Deferred() //建立一个Deferred对象 defer.next(function () { console.log('ok'); }).error(function (text) { console.log(text);//=> linkFly }).fail('linkFly');
实例方法
Deferred.prototype.next和Deferred.prototype.call
Deferred.prototype.next()构建一个全新的Deferred对象,并为它绑定成功事件处理函数,在没有调用Deferred.prototype.call()以前这个事件处理函数并不会执行。
var deferred = Deferred(); deferred.next(function (value) { console.log(value); // => linkFly }).call('linkFly');
Deferred.prototype.error和Deferred.prototype.fail
Deferred.prototype.error()构建一个全新的Deferred对象,并为它绑定失败事件处理函数,在没有调用Deferred.prototype.fail()以前这个事件处理函数并不会执行。
var deferred = Deferred(); deferred.error(function () { console.log('error');// => error }).fail();
静态方法。Deferred全部的静态方法,均可以使用Deferred.方法名()__的方式调用。__
Deferred.define(obj, list)
暴露静态方法到obj上,无参的状况下obj是全局对象:侵入性极强,但使用方便。list是一组方法,这组方法会同时注册到obj上。
Deferred.define();//无参,侵入式,默认全局对象,浏览器环境为window next(function () { console.log('ok'); });//静态方法入next被注册到了window下 var defer = {}; Deferred.define(defer);//非侵入式,Deferred的静态方法注册到了defer对象下 defer.next(function () { console.log('ok'); });
Deferred.isDeferred(obj)
判断对象obj是不是jsDeferred对象的实例(Deferred对象)。
Deferred.define(); console.log(Deferred.isDeferred({}));//=> false console.log(Deferred.isDeferred(wait(2)));//=> true
Deferred.call(fn[,args]*)
建立一个Deferred实例,而且触发其成功事件。fn是成功后要执行的函数,后续的参数表示传递给fn的参数。
call(function (text) { console.log(text);//=> linkFly }, 'linkFly'); console.log('hello,world!');// => 先输出
Deferred.next(fn)
建立一个Deferred实例,而且触发其成功事件。fn是成功后要执行的函数,它等同于只有一个参数的call,即:Deferred.call(fn)
Deferred.define(); next(function () { console.log('ok'); }); console.log('hello,world!');// => 先输出 //上面的代码等同于下面的代码 call(function () { console.log('ok'); }); console.log('hello,world!');// => 先输出
Deferred.wait(time)
建立一个Deferred实例,并等待time(秒)后触发其成功事件,下面的代码首先弹出"Hello,",2秒后弹出"World!"。
next(function () { alert('Hello,'); return wait(2);//延迟2s后执行 }). next(function (r) { alert('World!'); }); console.log('hello,world!');// => 先输出
Deferred.loop(n, fun)
循环执行n次fun,并将最后一次执行fun()的返回值做为Deferred实例成功事件处理函数的参数,一样loop中循环执行的fun()也是异步的。
loop(3, function () { console.log(count); return count++; }).next(function (value) { console.info(value);// => 2 }); //上面的代码也是异步的(无阻塞的) console.info('linkFly');
Deferred.parallel(dl[ ,fn]*)
把参数中非Deferred对象均转换为Deferred对象(经过Deferred.next()),而后并行触发dl中的Deferred实例的成功事件。
当全部Deferred对象均调用了成功事件处理函数后,返回的Deferred实例则触发成功事件,而且全部返回值将被封装为数组做为Deferred实例的成功事件处理函数的入参。
parallel()强悍之处在于它的并归处理,它能够将参数中屡次的异步最终并归到一块儿,这一点在JavaScript ajax嵌套中尤其重要:例如同时发送2条ajax请求,最终parallel()会并归这2条ajax返回的结果。
parallel()进行了3次重载:
下面一张图演示了Deferred.parallel的工做模型,它能够理解为合并了3次ajax请求。
Deferred.define(); parallel(function () { //等待2秒后执行 return wait(2).next(function () { return 'hello,'; }); }, function () { return wait(1).next(function () { return 'world!' }); }).next(function (values) { console.log(values);// => ["hello,", "world!"] });
当parallel传递的参数是一个对象的时候,返回值则是一个对象:
parallel({ foo: wait(1).next(function () { return 1; }), bar: wait(2).next(function () { return 2; }) }).next(function (values) { console.log(values);// => Object { foo=1, bar=2 } });
和jQuery.when()一模一样。
Deferred.earlier(dl[ ,fn]*)
当参数中某一个Deferred对象调用了成功处理函数,则终止参数中其余Deferred对象的触发的成功事件,返回的Deferred实例则触发成功事件,而且那个触发成功事件的函数返回值将做为Deferred实例的成功事件处理函数的入参。
注意:Deferred.earlier()并不会经过Deferred.define(obj)暴露给obj,它只能经过Deferred.earlier()调用。
Deferred.earlier()内部的实现和Deferred.parallel()大同小异,但值得注意的是参数,它接受的是Deferred,而不是parallel()的Function:
Deferred.define(); Deferred.earlier( wait(2).next(function () { return 'cnblog'; }), wait(1).next(function () { return 'linkFly' })//1s后执行成功 ).next(function (values) { console.log(values);// 1s后 => [undefined, "linkFly"] });
Deferred.repeat(n, fun)
循环执行fun方法n次,若fun的执行事件超过20毫秒则先将UI线程的控制权交出,等一下子再执行下一轮的循环。
本身跑了一下,跑出问题来了...duang...求道友指点下迷津
Deferred.define(); repeat(10, function (i) { if (i === 6) { var starTime = new Date(); while (new Date().getTime() - starTime < 50) console.info(new Date().getTime() - starTime);//到6以后时候不该该再执行了,由于这个函数的执行超过了20ms } console.log(i); //=> 0,1,2,3,4,5,6,7,8,9 });
Deferred.chain(args)
chain()方法的参数比较独特,能够接受多个参数,参数类型能够是:Function,Object,Array。
chain()方法比较难懂,它是将全部的参数构造出一条Deferred方法链。
例如Function类型的参数:
Deferred.define(); chain( function () { console.log('start'); }, function () { console.log('linkFly'); } ); //等同于 next(function () { console.log('start'); }).next(function () { console.log('linkFly'); });
它经过函数名来判断函数:
chain( //函数名!=error,则默认为next function () { throw Error('error'); }, //函数名为error function error(e) { console.log(e.message); } ); //等同于 next(function () { throw Error('error'); }).error(function (e) { console.log(e.message); });
也支持Deferred.parallel()的方式:
chain( [ function () { return wait(1); }, function () { return wait(2); } ] ).next(function () { console.log('ok'); }); //等同于 Deferred.parallel([ function () { return wait(1); }, function () { return wait(2); } ]).next(function () { console.log('ok'); });
固然能够组合参数:
chain( function () { throw Error('error'); }, //函数名为error function error(e) { console.log(e.message); }, //组合Deferred.parallel()的方式 [ function () { return wait(1); }, function () { return wait(2); } ] ).next(function () { console.log('ok'); }); //等同于 next(function () { throw Error('error'); }).error(function (e) { console.log(e.message); }); Deferred.parallel([ function () { return wait(1); }, function () { return wait(2); } ]).next(function () { console.log('ok'); });
Deferred.connect(funo, options)
将一个函数封装为Deferred对象,其目的是融入现有的异步编程。
注意:Deferred.connect()和Deferred.earlier()方法同样,并不会经过Deferred.define(obj)暴露给obj,它只能经过Deferred.connect()调用。官网使用了setTimeout的例子:
Deferred.connect()有两种重载:
var timeout = Deferred.connect(setTimeout, { target: window, ok: 0 }); timeout(1).next(function () { alert('after 1 sec'); }); //另一种传参 var timeout = Deferred.connect(window, "setTimeout"); timeout(1).next(function () { alert('after 1 sec'); });
Deferred.retry(retryCount, funcDeferred[ ,options])
调用retryCount次funcDeffered方法(返回值类型为Deferred),直到触发成功事件或超过尝试次数为止。
options参数是一个对象,{wait:number}指定每次调用等待的秒数。
注意:Deferred.retry()并不会经过Deferred.define(obj)暴露给obj,它只能经过Deferred.retry()调用。
Deferred.define(); Deferred.retry(3, function (number) {//Deferred.retry()方法是--i的方式实现的 console.log(number); return Deferred.next(function () { if (number ^ 1)//当number!=1的时候抛出异常,表示失败,number==1的时候则让它成功 throw new Error('error'); }); }).next(function () { console.log('linkFly');//=>linkFly });
从源码这一行能够看到做者重点照顾的是这些方法:
Deferred.methods = ["parallel", "wait", "next", "call", "loop", "repeat", "chain"];
其余的方法或许做者也以为有点勉强吧,在Deferred.define()中默认都没有暴露那些API。
原本就想写jsDeferred的API,结果读完了源码...篇幅缘由就不解读源码的,有兴趣的能够在下面的引用连接点过去看源码,不含注释未压缩版源码仅400行左右。
jsDeferred实现简单,代码通俗易懂,而API切割的很是容易上手,理念也容易理解,随着它的知名度提高进而让JavaScript异步编程备受瞩目,在阅读jsDeferred的时候,我老是在想这些前辈们当时苦苦思索走出JavaScript自留地的感受,从现代的眼光来看,相比Promise,可能jsDeferred的实现甚至于略显青涩。这也让我想起了Robert Nyman前辈最初编写getElementByClassName(),然而在当时看来,足够艳惊世界。
随着JavaScript的兴起,如今的咱们多喜欢四处扒来代码匆匆粘贴完成咱们大多数的任务,逐渐的丢失了本身思考和挖掘代码的能力。值得庆幸的是JavaScript正在凝结本身的精华,将来迢长路远,与君共勉。
下一篇将会讲解JavaScript异步编程的特性——控制反转。