资讯专栏INFORMATION COLUMN

Promise介绍--Deferred及jQuery篇

Darkgel / 1385人阅读

摘要:我们称为回调对象,它内部会维护一个数组,我们可以向其中添加若干个回调函数,然后在某一条件下触发执行。第一次之后,再次新的回调函数时,自动执行回调。当前面的回调函数返回时,终止后面的回调继续执行。

最近懒癌发作,说好的系列文章,写了一半,一直懒得写,今天补上一篇。

Deferred

我们在使用promise对象时,总会提到一个与它关系密切的对象——Deferred。其实Deferred没什么内容可讲的,其实很简单。

它包含一个promise对象

它可以改变对应的promise的状态

简单的实现如下:

class Deferred{
    constructor(){
        let defer = {};
        defer.promise = new Promise((resolve, reject)=>{
            defer.resolve = resolve;
            defer.reject = reject;
        })
        return defer;
    }
}

我们知道promise对象内部的状态,本身是在创建对象时传入的函数内控制,外部是访问不到的,Deferred对象在它的基础上包装了一层,并提供了两个在外部改变它状态的方法。

用法其实在Promise介绍--规范篇中的例子内,多处使用到了,这里就不再赘述。总觉得文章写这么点儿,就显得太水了。。所以借此,讲讲jQuery中的实现。

jQuery中还有一个静态方法$.Callbacks(),由于$.Deferred()强依赖它,所以我们先从它开刀。

$.Callbacks()

$.Callbacks()我们称为回调对象,它内部会维护一个数组,我们可以向其中添加若干个回调函数,然后在某一条件下触发执行。

有几个方法从名字我们就知道它的作用是什么,add向数组内部添加一个回调函数,empty清空数组,fire触发回调函数,has数组中是否已经添加某回调函数,remove从数组中删除某回调函数。

fireWith函数接收两个参数,第一个是回调函数执行的上下文,第二个是回传给回调函数的参数。fire中其实内部调用的就是fireWith,其中第一个参数传递的是this

其它的几个函数,都和回调数组的状态有关。创建Callbacks对象时,接收一个字符串或者对象作为参数。其实内部都会转换为对象,这里不赘述,不同字符串表示不同的处理方式,一一介绍。

once :对象只会调用一次。

let cb = $.Callbacks("once")
function a(){console.log("a")}
function b(){console.log("b")}
cb.add(a)
cb.fire()
cb.add(b)
cb.fire()
// a

第一次fire之后,回调列表之后不会再次触发。

memory : 记住回调列表的执行状态,如果回调函数fire过一次,之后每次add之后,则自动触发该回调。

let cb = $.Callbacks("memory")
function a(){console.log("a")}
function b(){console.log("b")}
cb.add(a)
cb.fire()
// a
cb.add(b)
// b

第一次fire之后,再次add新的回调函数b时,自动执行回调b

unique:每一个回调函数只可以添加一次。

let cb = $.Callbacks("unique")
function a(){console.log("a")}
function b(){console.log("b")}
cb.add(a)
cb.add(a)
cb.fire()
// a
cb.add(b)
cb.fire()
// a
// b

第一次fire时,只会打印一个a,说明第二个a没有添加成功,但当我们添加b时,是可以添加成功的。

stopOnFalse:当前面的回调函数返回false时,终止后面的回调继续执行。

let cb = $.Callbacks("stopOnFalse")
function a(){console.log("a");return false;}
function b(){console.log("b")}
cb.add(a)
cb.add(b)
cb.fire()
// a

函数a返回了false,导致函数b没有执行。

我们再回过头看$.Callbacks()对象的方法,lock方法表示锁住回调数组,不再执行,也就是模式为once时,调用一次fire后的状态,即在此之后不可以在此触发。如下:

let cb = $.Callbacks()
function a(){console.log("a")}
function b(){console.log("b")}
cb.add(a)
cb.fire()
cb.lock()
cb.add(b)
cb.fire()

lock之后,再次添加函数b并调用fire时,不会再次执行,与once模式下效果类似。但如果是memory模式,回调先fire,然后再lock,之后再次add时,新添加的函数依然会执行。

let cb = $.Callbacks("memory")
function a(){console.log("a")}
function b(){console.log("b")}
cb.add(a)
cb.fire()
cb.lock()
cb.add(b)
// a
// b

其实这种效果和直接创建回调对象时,参数设为once memory是一致的。也就是说,如下代码与上面效果一致。

let cb = $.Callbacks("once memory")
function a(){console.log("a")}
function b(){console.log("b")}
cb.add(a)
cb.fire()
cb.add(b)
// a
// b

我们发现这种once memory模式,正好与Promisethen方法添加的回调很类似。如果promise对象处于pending状态,则then方法添加的回调存储在一个数组中,当promise对象状态改变(fire)时,执行相应的回调,且之后再次通过then方法添加回调函数,新回调会立刻执行。同时,每一个回调只能执行一次。所以,$.Deferred()内部用的正好是once memoryCallbacks

还有一个函数叫做disable,它的作用是直接禁用掉这个回调对象,清空回调数组,禁掉fireadd等。

locked用于判断数组是否被锁住,返回truefalsedisabled用于判断回调对象是否被警用,同样返回truefalse

$.Deferred()

有了以上的基础,我们接下来看看jQuery中,Deferred对象的实现。我们先看看它都有哪些方法。如图:

别的方法我们暂且不关注,我们注意到里面有四个我们比较熟悉的方法,promiserejectresolve。它们和我们前面说的Deferred对象中作用差不多。

jQueryDeferred中有三个添加回调函数的方法donefailprogress,分别对应添加promise状态为resolvedrejectedpending时的回调,同时对应有三个触发回调函数的方法resolverejectnotify

我们接下来看看它内部是怎么实现的。首先为每种状态分别创建一个Callbacks对象,如下:

  var tuples = [
      // action, add listener, listener list, final state
     ["resolve", "done", jQuery.Callbacks("once memory"), "resolved"],
     ["reject", "fail", jQuery.Callbacks("once memory"), "rejected"],
     ["notify", "progress", jQuery.Callbacks("memory")]
]

我们发现donefail对应的回调对象是once memory,而progress对应的是memory。说明通过progress添加的函数,可以多次重复调用。

然后定义了一个state用来保存状态,以及内部的一个promise对象。

state = "pending",
promise = {
    state: function() {
        return state;
    },
    always: function() {
        deferred.done(arguments).fail(arguments);
        return this;
    },
    then: function( /* fnDone, fnFail, fnProgress */ ) {
    },

    // Get a promise for this deferred
    // If obj is provided, the promise aspect is added to the object
    promise: function(obj) {
        return obj != null ? jQuery.extend(obj, promise) : promise;
    }
},

接下来会执行一个循环,如下:

// Add list-specific methods
jQuery.each(tuples, function(i, tuple) {
    var list = tuple[2],
        stateString = tuple[3];

    // promise[ done | fail | progress ] = list.add
    promise[tuple[1]] = list.add;

    // Handle state
    if (stateString) {
        list.add(function() {

            // state = [ resolved | rejected ]
            state = stateString;

            // [ reject_list | resolve_list ].disable; progress_list.lock
        }, tuples[i ^ 1][4].disable, tuples[2][5].lock);
    }

    // deferred[ resolve | reject | notify ]
    deferred[tuple[0]] = function() {
        deferred[tuple[0] + "With"](this === deferred ? promise : this, arguments);
        return this;
    };
    deferred[tuple[0] + "With"] = list.fireWith;
});

这一段代码中我们可以看出,donefailprogress其实就是addresolverejectnotify其实就是fire,与之对应的resolveWithrejectWithnotifyWith其实就是fireWith。且成功和失败的回调数组中,会预先添加一个函数,用来设置promise的状态和禁用掉其它状态下的回调对象。

从上面这段代码中我们也可以看出,添加回调函数的方法,都是添加在promise对象上的,而触发回调的方法是添加在deferred对象上的。代码中会通过如下方法,把promise对象的方法合并到deferred对象上。

promise.promise(deferred);

所以,我们打印一下$.Deferred().promise()

发现它确实比$.Deferred()少了那几个触发回调的方法。

其它的几个方法我们简单说一下,always会同时在成功和失败的回调数组中添加方法。state是查看当前promise对象的状态。

then方法如下:

then: function( /* fnDone, fnFail, fnProgress */ ) {
    var fns = arguments;
    return jQuery.Deferred(function(newDefer) {
        jQuery.each(tuples, function(i, tuple) {
            var fn = jQuery.isFunction(fns[i]) && fns[i];

            // deferred[ done | fail | progress ] for forwarding actions to newDefer
            deferred[tuple[1]](function() {
                var returned = fn && fn.apply(this, arguments);
                if (returned && jQuery.isFunction(returned.promise)) {
                    returned.promise()
                        .progress(newDefer.notify)
                        .done(newDefer.resolve)
                        .fail(newDefer.reject);
                } else {
                    newDefer[tuple[0] + "With"](
                        this === promise ? newDefer.promise() : this,
                        fn ? [returned] : arguments
                    );
                }
            });
        });
        fns = null;
    }).promise();
}

从代码中我们可以看出,then方法和规范中的then方法类似,不过这里多了第三个参数,是用于给progress添加回调函数,同时返回一个新的promise对象。

pipe是为了向前兼容,它与then是相等的。

$.when()

jQuery中还有一个与之相关的方法$.when(),它的作用类似于Promise.all()。具体实现方式基本是新建了一个Deferred对象,然后遍历所有传递进去的promise对象。不过添加了progres
的处理。总之和规范有很多的不同,大家有兴趣的就自己看一下吧。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/86653.html

相关文章

  • jQuery Deferred

    摘要:比较下和也就是说返回值是的一个非状态操作的子集,允许我们添加回调,但是不允许我们操作的状态。前面说了的返回值是一个新的对象,如果在新的对象上继续添加回调会怎么样呢我们分两种情况来看。方法的返回值不是对象的返回值会传递给的参数。 前言 Deferred是从1.5版本引入的一个核心特性之一,主要是为了解决Callback Hell,老生常谈的问题,这里就不多赘述了。本文旨在剖析Deferr...

    HollisChuang 评论0 收藏0
  • jquery ajax 方法封装说明

    摘要:简要说明前面我写了一篇方法封装及文件设计文档,主要用来说明我们在项目中通常会对的方法进行进一步的封装处理,便于我们在业务代码中使用。这篇文档我们主要对封装的方法进行一个简要说明。 简要说明 前面我写了一篇《jquery ajax 方法封装及 api 文件设计》文档,主要用来说明我们在项目中通常会对 jquery 的 ajax 方法进行进一步的封装处理,便于我们在业务代码中使用。从那篇文...

    z2xy 评论0 收藏0
  • jQuery源码解析Deferred异步对象

    摘要:回调队列对象,用于构建易于操作的回调函数集合,在操作完成后进行执行。对象对象,用于管理回调函数的多用途列表。如果传入一个延迟对象,则返回该对象的对象,可以继续绑定其余回调,在执行结束状态之后也同时调用其回调函数。 在工作中我们可能会把jQuery选择做自己项目的基础库,因为其提供了简便的DOM选择器以及封装了很多实用的方法,比如$.ajax(),它使得我们不用操作xhr和xdr对象,直...

    Coding01 评论0 收藏0
  • jQuery Deferred对象

    摘要:给普通的操作指定回调函数对象的最大优点,就是它把这一套回调函数接口,从操作扩展到了所有操作。方法用于指定对象状态为已失败时的回调函数。执行完毕执行成功执行失败接收一个或多个对象作为参数,为其指定回调函数。 什么是deferred对象 开发网站的过程中,我们经常遇到某些耗时很长的javascript操作。其中,既有异步的操作(比如ajax读取服务器数据),也有同步的操作(比如遍历一个大型...

    baoxl 评论0 收藏0
  • 异步 JavaScript 与 Promise

    摘要:为这些回调函数分别命名并分离存放可以在形式上减少嵌套,使代码清晰,但仍然不能解决问题。如果在一个结束成功或失败,同前面的说明后,添加针对成功或失败的回调,则回调函数会立即执行。 异步? 我在很多地方都看到过异步(Asynchronous)这个词,但在我还不是很理解这个概念的时候,却发现自己常常会被当做已经很清楚(* ̄ロ ̄)。 如果你也有类似的情况,没关系,搜索一下这个词,就可以得到大致...

    livem 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<