Fork me on GitHub

延时调用--deferred.js原码分析

有些时候,我们需要等待上一个操作完成之后,才能进行下一步的操作。比如Ajax实现自动提交表单操作的时候,程序需要等待,一旦有返回结果了,则继续进行一下步操作。

这时deferred.js这个库就产生了,当然,jquery也有这个功能。下面就分析一下这个库的原理:

/**
 * @fileOverview JSDeferred
 * @author       cho45@lowreal.net
 * @version      0.4.0
 * @license
 * JSDeferred Copyright (c) 2007 cho45 ( www.lowreal.net )
 *
 *  针对deferred的原理进行精简
 */ ; // no warnings for uglify

function Deferred () {
    return this.init();
}
//定义静态方法
Deferred.ok = function(x) {return x} //缺省的成功回调
Deferred.ng = function(x) {throw x}
//判断是否为Deferred的实例
Deferred.isDeferred = function (obj) {
    return !!(obj && obj._id === Deferred.prototype._id);
};

//这个next是挂在Deferred上的静态方法。与实列方法.next是不同的
Deferred.next = function(fn){
    var d = new Deferred();
    var img = new Image();
    var handler = function(){
        d.canceller();
        d.calls();
    }
    //这个地方个人认为比较巧秒,它利用了img加载成功或错误回调具有异步的特性。
    //保证完整收集这些.next().next()...
    //事实上官方还用了其它两种方式,确保兼容,如setTimeout....
    img.addEventListener('error',handler,false);
    d.canceller = function(){
        img.removeEventListener('error',handler,false);
    }
    //这里用来触发一个img的加载事件
    img.src = "data:image/png," + Math.random();
    if(fn) d.callback.ok = fn;
    return d;
}

//这里是用来模拟一个比较耗时的异步过程
//实践中,可能是取数据的过程,如等待ajax回调
Deferred.wait = function (n) {
    var d = new Deferred(), t = new Date();
    var id = setTimeout(function () {
        d.calls((new Date()).getTime() - t.getTime());
    }, n * 1000);
    d.canceller = function () { clearTimeout(id) };
    return d;
};


Deferred.prototype = {
    _id : 8888, //随便填写,用来判断是否为Deferred的实例
    init : function(){
        this._next = null;
        //使Deferred.isDeferred 判断为假
        this.callback = {
            ok : Deferred.ok,
            ng : Deferred.ng
        }
        return this;
    },
    next  : function (fun) { return this._post("ok", fun) },

    calls  : function (val) { 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) {
        var next = "ok";

        value = this.callback[okng](value);
        //这里的value如果不是Deferred的实例
        if (Deferred.isDeferred(value)) {
            //加载下一个任务
            value._next = this._next;
        } else {
            //说明没有下一个任务了
            if (this._next) this._next._fire(next, value);
        }
        return this;
    }
}
 

 为了配合分析,先给一段测试代码,方便追踪它的流程。

Deferred.next(function(){
    alert(1)
    return Deferred.wait(3)
}).next(function(){
    alert(2)
})

这里用了一个链式写法,熟jquery的人,对此一定不会陌生。首先是调用Deferred.next()这个方法,返回一个Deferred的实例,然后调用实例上的next()方法。

意图是要执行第一个函数(弹出1)之后,再执行第二函数(弹出2),仅管在写法是同步的,但是执行的时候却是异步的。 你或许会问, 为什么不直接用setTimeout呢?

这个问题非常好,其实在很情况下,我觉得确实是可以等价交换。但是setTimeout有一个使用前提,那就是我们必需事先知道上一步运行完成需要等多长时间。而Deferred不需要。

这时你或许又会问,那为什么不用回调呢?这回我终于没有办法说什么了。直接演示一下这种写法:

function(next1,next2,next3,...){
    //....
    //....
    next1(function(){
        next2(function(){
            next3(...);
        })
    })
}

像竹笋一样,一层套一层。如果函数体长一点,看着不晕吗?再看看deferred的写法:

Deferred.next(function(){
    next1();
}).next(function(){
    next2();
}).next(function(){
    next3()
}).next(function(){
    //...
})

像麻将一样,一字排开,是时髦的链式用法。

如果你觉得怎么写无所谓,那么要结合起来看了,如果next1是一个耗时不确定的操作,但是要保证执行顺序。怎么破?

不管你怎么破,反正我是选择Deferred,用定了。

posted on 2015-01-15 15:13  bjtqti  阅读(664)  评论(0编辑  收藏  举报