4.Deferred异步回调原理分析及实现
Geek-James opened this issue · 0 comments
jQuery 源码解析代码及更多学习干货: 猛戳GitHub
本篇代码为 my-jQuery 1.0.4.js
建议阅读本篇先弄懂上一篇Callbacks 原理分析,因为Deferred异步回调是基于Callbacks。下载源码然后根据文章思路学习,最好自己边思考边多敲几遍。
一、基本概念
Promise/A+规范
首先推荐各位阅读一下 Promise/A+规范
Promise作为一个模型,提供了一个在软件工程中描述的延时概念的解决方案。
- 1.Promise表示一个异步操作的最终结果
- 2.与Promise最主要的交互方法是,通过将函数传入他的then方法,从而获得Promise最终的值或Promise最终拒绝的值的原因。
- 3.一个Promise必须处于以下几个状态之中:pending,fulfilled,rejected.
- 4.一个Promise必须提供一个then方法来获取其值或原因。
Promise和Deferred的关系
Promise是ES6提出的异步编程模式,可以参考阮一峰ES6- Promise
Deferred是jQuery提出的回调函数解决方案,主要依赖Callbacks回调,可以参考上一篇Callbacks原理解析。
主要解决的问题是:当一个异步依赖于另一个异步请求的结果时,或者某个操作需要等另外几个操作都结束后才开始等,更强大的是从ajax操作扩展到了所有操作,动画、定时中也常用。
二、开始剖析 Deferred
1.Deferred 提供的API
(1) $.Deferred()
生成一个deferred对象。
(2) deferred.done()
指定操作成功时的回调函数
(3) deferred.fail()
指定操作失败时的回调函数
(4) .promise()
返回一个Promise对象来观察当某种类型的所有行动绑定到结合,排队与否还是已经完成。
(5) deferred.resolve()
手动改变deferred对象的运行状态为"已完成",从而立即触发done()
方法。
(6)deferred.reject()
这个方法与deferred.resolve()
正好相反,调用后将deferred对象的运行状态变为"已失败",从而立即触发fail()
方法。
(7) $.when()
为多个操作指定回调函数。
除了这些方法以外,deferred对象还有二个重要方法,上面的教程中没有涉及到。
(8)deferred.then()
有时为了省事,可以把done()
和fail()
合在一起写,这就是then()
方法。
(9)deferred.progress()
当Deferred(延迟)对象生成时,调用已添加的处理程序。
2. jQuery封装用法
// jQuery Deferred 写法
var dtd = $.Deferred();// 新建一个Deferred对象
var wait = function(dtd){
var tasks = function(){
alert("执行完毕");
dtd.resolve();//改变Deferred对象的执行状态 成功状态
};
setTimeout(tasks,2000);
return dtd;
};
// 延迟对象的状态 决定调用哪个队列的处理函数
$.when(wait(dtd))
.done(function(){
alert("成功啦");
}).fail(function(){
alert("出错了");
})
// dtd.resolve(); 改变dtd的执行状态导致done立刻执行
// dtd.reject(); 改变dtd的执行状态导致fail立刻执行
// dtd.resolve();
以上代码输出:
执行完毕
成功啦
通过以上代码,我们反推jQuery的源码是如何实现的。
3.源码的设计思路:
以下仅为核心代码片段,完整代码请点击源码分析下载
(1)首先定义了tuples的数据结构,用来组装存储异步延迟三种不同状态信息的描述.
/**
* tuples 定义一个数组来存储三种不同状态信息的描述
* 第一个参数 延时对象的状态
* 第二个参数 往队列里添加处理函数
* 第三个参数 创建不同状态的队列
* 第四个参数 记录最终状态信息
* **/
var tuples = [
["resolve","done",jQuery.Callbacks("once memory"),"resolved"],
["reject","fail",jQuery.Callbacks("once memory"),"rejected"],
["notify","progress",jQuery.Callbacks("memory")]]
(2)然后定义一个promise
用来封装state,then,promise
对象
promise = {
state :function(){
return state;
},
then:function() {
},
promise:function(obj) {
console.log(promise);
debugger
return obj !=null ? jQuery.extend(obj,promise):promise;
}
},
(3)定义一个延迟对象 deferred = {};
(4)遍历封装好的tuples数组队列,把数组里第二个元素也就是映射到Callbacks并且给到list,将数组里第三个元素记录最终状态的信息给到stateString,然后把数组第一个元素即延时对象的状态映射到Callbacks的add方法上,定义辅助方法deferred[resolveWith]
,deferred[rejectWith]
,deferred[notifyWith]
,最后调用Callbacks的fireWith方法实现队列的回调。
// 遍历 tuples
tuples.forEach(function(tuple,i){
var list = tuple[2], // 创建队列 创建三次 self对象的引用 映射 调用Callbacks里面的方法
stateString = tuple[3]; // 拿到当前最终信息的描述
// promise[done | fail |progress] 将这三个状态都拿到Callbacks self里面方法的引用 添加处理函数
promise[tuple[1]] = list.add;
// Handle state 成功或者失败
if (stateString) { //添加第一个处理程序
list.add(function(){
// state = [resolved | rejected]
state = stateString;
});
}
// deferred [resolve | reject | notify ] 延时对象的状态拿到函数的引用
deferred[tuple[0]] = function(){
deferred[tuple[0] + "With"] (this === deferred ? promise : this, arguments);
return this;
};
// list.fireWith 执行队列并且传参
// 调用队列中的处理函数 并且给他们传参 绑定执行时的上下文对象
deferred[tuple[0] + "With"] = list.fireWith;
});
(5)将deferred返回出去
// Make the deferred a promise
promise.promise(deferred);
return deferred;
(6)定义一个when方法
// 执行一个或多个对象的延迟函数的回调函数
when : function(subordinate){
return subordinate.promise();
}
});
至此,大功告成,实现了jQuery的源码剖析,体会了到作者的数据结构队列处理编程**
Deferred设计图:
其他
jQuery 源码剖析 系列目录地址:猛戳GitHub
jQuery 源码剖析 系列预计写十篇左右,旨在加深对原生JavaScript 部分知识点的理解和深入,重点讲解 jQuery核心功能函数、选择器、Callback 原理、延时对象原理、事件绑定、jQuery体系结构、委托设计模式、dom操作、动画队列等。
如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star⭐️,对作者也是一种鼓励。