注:由于 Deferred
需要用到 Callback
因此需要先了解 Callback
的实现以及用处后才能进一步了解 Deferred
的实现及其作用。
Deferred (延迟对象)解决了什么
在写 javascript
代码时,难免的回写到异步回调。
x
需要异步获取,而 y
轴坐标需要依赖 x
轴,z
轴需要依赖 y
轴,那么写起来的代码会是这样: var x = 10;function getX(cb) { setTimeout(function() { cb && cb(10); })}// 假设对X做的操作仅仅是 +1function getY(x, cb) { setTimeout(function() { cb && cb(x + 1); })}// 假设对Y做的操作仅仅是 -5function getZ(y, cb) { setTimeout(function() { cb && cb(y - 5); })}getX(function(result1) { var x = result1; getY(x, function(result2) { var y = result2; getZ(y, function(result3) { var z = result3; var point = { x: x, y: y, z: z }; doSomeThing(point); }) })})
这种结构有缩进看着好像也就那么回事,但是没缩进呢?
getX(function(result1){var a = result1;getY(a.x, function(result2){var b = result2;getZ(b.y, function(result3){var c = result3;var point = {x: a.x, y: b.y, z: c.z};doSomeThing(point);})})})
真是一坨啊,而且这还是仅仅3个回调,不仅语义不明确,而且可读性极低。而 Deferred
的出现就解决了这个问题。
下面就是同样的例子,使用 Deferred
实现:
var x = 10;var point = {};function getX() { var defer = $.Deferred(); setTimeout(function() { point.x = x; defer.resolve(x); }) return defer.promise();}function getY(x) { var defer = $.Deferred(); setTimeout(function() { point.y = x + 1; defer.resolve(x + 1); }) return defer.promise();}function getZ(y) { var defer = $.Deferred(); setTimeout(function() { point.z = y - 5; defer.resolve(y - 5); }) return defer.promise();}getX().then(getY).then(getZ).then(function() { doSomeThing(point);});
逻辑写在了函数的内部,不需要更多的回调来处理数据,而且即使在缩进错误的情况下,也毫无问题。
Deferred 都做了什么?
Deferred
的3个重要的方法在图上都显现了,下面介绍下这3个方法都做了什么
promise.then(fun1,fun2)
将 fun1
处理后添加到 resolveCallbacks
中,将 fun2
处理后添加到 rejectCallbacks
中,当 promise
状态改变时,也就是 resolve
或是 reject
方法执行后,执行对应的 Callbacks
,一般 reject
处理错误数据, resolve
处理正确数据。
defer.resolve(obj)
通知 promise
处理 obj
数据,使用 fun1
处理
defer.reject(err)
通知 promise
处理 err
数据,使用 fun2
处理
简单实现
以下代码为 jQuery 3.1.0
中,关于 Deferred
源码中的一部分。经过我的一些修改,仅仅实现了主要的逻辑。
由于代码量感觉略大,这里就不放上来了,具体的精简代码 。
其实看看还是能在精简点的,但是在精简下去就要改变 JQuery
中源码的编写方式,不利于理解 JQuery
源码了 。
具体的代码执行过程,在代码里都有,这里上个图来说明下,一个 Deferred
从创建到 then
执行,到返回新的 Deferred
的过程:
配合思维导图来看,应该更能理解:
JQ 中的实现
当然在 JQuery
中 Deferred
的实现远比上面的简单实现要来的复杂。但主体的逻辑和上面已经没有区别了。
下面说说主要区别有哪些地方
为
resolve
和reject
各自分别实现了两个Callbacks
,这两个Callback
其中一个用于Deferred
内部,一个用于then
方法调用时,这样的结构使得代码管理起来更容易。功能性函数的添加,这个在下面一节中细说。
实现了
3
种状态,除了resolve
和reject
外还实现了notify
的状态。处理了
Deferred
实际允许过程种,由于特殊的写法而导致的一些意料之外的BUG
。
notify 的作用
notify
用于提供 Deferred
的一些状态信息,比如当前的 Deferred
的完成百分比,以及 ajax
的状态码的改变··· 但是 notify
和其他两种状态并不互斥,而其他两种的改变也不会影响 notify
状态。
功能性函数
.done(fun) | .fail(fun) | .progress(fun)
这3个方法分别对应 resolve | reject | notify
这3种状态下在 Deferred
内部使用的 Callbacks
的 .add
方法,用于在相应状态时,执行我们传入的函数。相当于对外暴露了 Callbacks
的 .add
方法。
.state()
用于获取 Deferred
的状态。
.always()
为 .done(arguments).fail(arguments)
的变体,表示 Deferred
变为 resolve | reject
状态都要执行的函数。
.catch()
为 .then(null, fn)
的变体,表示仅仅配置 reject
需要执行的函数。(通常用于捕获异常)
$.when(promises)
当传入的 promises
全部运行成功后,才调用 resolved
状态的回调函数,如果其中一个 promise
失败,就调用 rejected
状态的回调函数。