jQuery源码剖析(四) - Deferred异步回调解决方案

1,807 阅读4分钟

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⭐️,对作者也是一种鼓励。

关注公众号回复:学习 领取前端最新最全学习资料,也可以进群和大佬一起学习交流