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
- 参考和引用
jsDeferred和Promise/A+
在上一篇文章《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源码剖析》):web
Promise/A+
- Promise是基于状态的
- 状态标识:pending(初始状态)、fulfilled(成功状态)和rejected(失败状态)。
- 状态为单方向移动“pending->fulfilled”,”pending->rejected”。
- 因为存在状态标识,因此支持晚事件处理的晚绑定。
jsDeferred
- jsDeferred是基于事件的,并无状态标识
- 实例的成功/失败事件是基于事件触发而被调用
- 由于没有状态标识,因此能够屡次触发成功/失败事件
- 不支持晚绑定
jsDeferred的工做模型
下面一张图粗略演示了jsDeferred的工做模型。ajax
下面涉及到jsDeferred的源码,对于第一次接触的童鞋请直接拉到API一节(下一节),读完了API再来看这里。编程
jsDeferred第一次调用next有着不一样的处理,jsDeferred在第一次调用next()的时候,会当即异步执行这个回调函数——而这个挂起异步,则视当前的环境(如浏览器最佳环境)选择最优的异步挂起方案,例如现代浏览器下会经过建立Image对象的方式来进行异步挂起,摘录源码以下:segmentfault
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()源码:api
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()底层
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 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对象。
1
2
3
4
5
6
7
8
|
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()以前这个事件处理函数并不会执行。
1
2
3
4
|
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()以前这个事件处理函数并不会执行。
1
2
3
4
|
var
deferred = Deferred();
deferred.error(
function
() {
console.log(
'error'
);
// => error
}).fail();
|
静态方法。Deferred全部的静态方法,均可以使用Deferred.方法名()的方式调用。 Deferred.define(obj, list)
暴露静态方法到obj上,无参的状况下obj是全局对象:侵入性极强,但使用方便。list是一组方法,这组方法会同时注册到obj上。
1
2
3
4
5
6
7
8
9
10
|
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对象)。
1
2
3
|
Deferred.define();
console.log(Deferred.isDeferred({}));
//=> false
console.log(Deferred.isDeferred(wait(2)));
//=> true
|
Deferred.call(fn[,args]*)
建立一个Deferred实例,而且触发其成功事件。fn是成功后要执行的函数,后续的参数表示传递给fn的参数。
1
2
3
4
|
call(
function
(text) {
console.log(text);
//=> linkFly
},
'linkFly'
);
console.log(
'hello,world!'
);
// => 先输出
|
Deferred.next(fn)
建立一个Deferred实例,而且触发其成功事件。fn是成功后要执行的函数,它等同于只有一个参数的call,即:Deferred.call(fn)
1
2
3
4
5
6
7
8
9
10
11
|
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!”。
1
2
3
4
5
6
7
8
|
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()也是异步的。
1
2
3
4
5
6
7
8
|
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次重载:
- parallel(fn[ ,fn]*):传入Function类型的参数,容许多个
- parallel(Array):给定一个由Function组成的Array类型的参数
- parallel(Object):给定一个对象,由对象中全部可枚举的Function构建Deferred
下面一张图演示了Deferred.parallel的工做模型,它能够理解为合并了3次ajax请求。
1
2
3
4
5
6
7
8
9
|
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传递的参数是一个对象的时候,返回值则是一个对象:
1
2
3
4
5
6
7
8
9
10
|
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.earlier(Deferred[ ,Deferred]*):传入Deferred类型的参数,容许多个
- Deferred.earlier(Array):给定一个由Deferred组成的Array类型的参数
- Deferred.earlier(Object):给定一个对象,由对象中全部可枚举的Deferred构建Deferred
1
2
3
4
5
6
7
|
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…求道友指点下迷津
1
2
3
4
5
6
7
8
|
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类型的参数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
Deferred.define();
chain(
function
() {
console.log(
'start'
);
},
function
() {
console.log(
'linkFly'
);
}
);
//等同于
next(
function
() {
console.log(
'start'
);
}).next(
function
() {
console.log(
'linkFly'
);
});
|
它经过函数名来判断函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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()的方式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
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'
);
});
|
固然能够组合参数:
1
2
3
4
5
6
|