jQuery 1.5带来了新的Deferred对象和附加方法.when、.Deferred和. _deferred。

对于那些以前没有使用过. deferred的人,我已经为它注释了源代码。

这些新方法的可能用途是什么,我们如何将它们纳入模式?

我已经阅读了API和源代码,所以我知道它是做什么的。我的问题是我们如何在日常代码中使用这些新特性?

我有一个简单的缓冲区类示例,它按顺序调用AJAX请求。(上一个结束后下一个开始)。

/* Class: Buffer
 *  methods: append
 *
 *  Constructor: takes a function which will be the task handler to be called
 *
 *  .append appends a task to the buffer. Buffer will only call a task when the 
 *  previous task has finished
 */
var Buffer = function(handler) {
    var tasks = [];
    // empty resolved deferred object
    var deferred = $.when();

    // handle the next object
    function handleNextTask() {
        // if the current deferred task has resolved and there are more tasks
        if (deferred.isResolved() && tasks.length > 0) {
            // grab a task
            var task = tasks.shift();
            // set the deferred to be deferred returned from the handler
            deferred = handler(task);
            // if its not a deferred object then set it to be an empty deferred object
            if (!(deferred && deferred.promise)) {
                deferred = $.when();
            }
            // if we have tasks left then handle the next one when the current one 
            // is done.
            if (tasks.length > 0) {
                deferred.done(handleNextTask);
            }
        }
    }

    // appends a task.
    this.append = function(task) {
        // add to the array
        tasks.push(task);
        // handle the next task
        handleNextTask();
    };
};

我正在寻找. deferred和.when的演示和可能的用法。

看到. _deferred的例子也很不错。

链接到新的jQuery。Ajax源代码的例子是作弊。

我特别感兴趣的是,当我们抽象出一个操作是同步完成还是异步完成时,可以使用哪些技术。


当前回答

我能想到的最佳用例是缓存AJAX响应。以下是Rebecca murphy关于该主题的介绍帖子的修改示例:

var cache = {};

function getData( val ){

    // return either the cached value or jqXHR object wrapped Promise
    return $.when(
        cache[ val ] || 
        $.ajax('/foo/', {
            data: { value: val },
            dataType: 'json',
            success: function( resp ){
                cache[ val ] = resp;
            }
        })
    );
}

getData('foo').then(function(resp){
    // do something with the response, which may
    // or may not have been retrieved using an
    // XHR request.
});

基本上,如果值已经被请求过一次,然后立即从缓存中返回。否则,AJAX请求将获取数据并将其添加到缓存中。当美元/。然后不关心这些;您需要关心的是使用响应,在这两种情况下,响应都传递给.then()处理程序。jQuery.when()将非promise /Deferred处理为Completed,立即执行链上的.done()或.then()。

当任务可能异步操作,也可能不异步操作时,延迟非常适合,并且您希望从代码中抽象出该条件。

另一个使用$的真实例子。当助手:

$.when($.getJSON('/some/data/'), $.get('template.tpl')).then(function (data, tmpl) {

    $(tmpl) // create a jQuery object out of the template
    .tmpl(data) // compile it
    .appendTo("#target"); // insert it into the DOM

});

其他回答

您可以使用延迟对象来实现在webkit浏览器中工作良好的流动设计。Webkit浏览器将为窗口调整大小的每个像素触发调整大小事件,不像FF和IE只对每次调整大小触发一次事件。因此,您无法控制绑定到窗口调整大小事件的函数的执行顺序。这样就解决了问题:

var resizeQueue = new $.Deferred(); //new is optional but it sure is descriptive
resizeQueue.resolve();

function resizeAlgorithm() {
//some resize code here
}

$(window).resize(function() {
    resizeQueue.done(resizeAlgorithm);
});

这将序列化代码的执行,以便它按照您的意图执行。在将对象方法作为回调传递给deferred对象时,要当心陷阱。一旦这样的方法作为deferred的回调执行,'this'引用将被覆盖为对deferred对象的引用,并且不再引用该方法所属的对象。

这里是一个与ehynd的答案略有不同的AJAX缓存实现。

正如fortuneRice的后续问题所指出的,如果请求在其中一个返回之前执行,ehynd的实现实际上并不能阻止多个相同的请求。也就是说,

for (var i=0; i<3; i++) {
    getData("xxx");
}

如果“xxx”的结果之前没有被缓存过,很可能会导致3个AJAX请求。

这可以通过缓存请求的deferred而不是结果来解决:

var cache = {};

function getData( val ){

    // Return a promise from the cache (if available)
    // or create a new one (a jqXHR object) and store it in the cache.
    var promise = cache[val];
    if (!promise) {
        promise = $.ajax('/foo/', {
            data: { value: val },
            dataType: 'json'
        });
        cache[val] = promise;
    }
    return promise;
}

$.when(getData('foo')).then(function(resp){
    // do something with the response, which may
    // or may not have been retreived using an
    // XHR request.
});

这是一个自我宣传的答案,但我花了几个月的时间研究这个问题,并在2012年旧金山jQuery大会上展示了结果。

这是一个免费视频的谈话:

https://www.youtube.com/watch?v=juRtEEsHI9E

另一个使用deferred来实现任何类型的计算(通常是一些性能密集型或长时间运行的任务)的缓存的例子:

var ResultsCache = function(computationFunction, cacheKeyGenerator) {
    this._cache = {};
    this._computationFunction = computationFunction;
    if (cacheKeyGenerator)
        this._cacheKeyGenerator = cacheKeyGenerator;
};

ResultsCache.prototype.compute = function() {
    // try to retrieve computation from cache
    var cacheKey = this._cacheKeyGenerator.apply(this, arguments);
    var promise = this._cache[cacheKey];

    // if not yet cached: start computation and store promise in cache 
    if (!promise) {
        var deferred = $.Deferred();
        promise = deferred.promise();
        this._cache[cacheKey] = promise;

        // perform the computation
        var args = Array.prototype.slice.call(arguments);
        args.push(deferred.resolve);
        this._computationFunction.apply(null, args);
    }

    return promise;
};

// Default cache key generator (works with Booleans, Strings, Numbers and Dates)
// You will need to create your own key generator if you work with Arrays etc.
ResultsCache.prototype._cacheKeyGenerator = function(args) {
    return Array.prototype.slice.call(arguments).join("|");
};

下面是一个使用这个类来执行一些(模拟繁重的)计算的例子:

// The addingMachine will add two numbers
var addingMachine = new ResultsCache(function(a, b, resultHandler) {
    console.log("Performing computation: adding " + a + " and " + b);
    // simulate rather long calculation time by using a 1s timeout
    setTimeout(function() {
        var result = a + b;
        resultHandler(result);
    }, 1000);
});

addingMachine.compute(2, 4).then(function(result) {
    console.log("result: " + result);
});

addingMachine.compute(1, 1).then(function(result) {
    console.log("result: " + result);
});

// cached result will be used
addingMachine.compute(2, 4).then(function(result) {
    console.log("result: " + result);
});

同样的底层缓存也可以用于缓存Ajax请求:

var ajaxCache = new ResultsCache(function(id, resultHandler) {
    console.log("Performing Ajax request for id '" + id + "'");
    $.getJSON('http://jsfiddle.net/echo/jsonp/?callback=?', {value: id}, function(data) {
        resultHandler(data.value);
    });
});

ajaxCache.compute("anID").then(function(result) {
    console.log("result: " + result);
});

ajaxCache.compute("anotherID").then(function(result) {
    console.log("result: " + result);
});

// cached result will be used
ajaxCache.compute("anID").then(function(result) {
    console.log("result: " + result);
});

您可以在这个jsFiddle中使用上述代码。

我只是在实际代码中使用了Deferred。在jQuery终端项目中,我有函数exec调用用户定义的命令(就像他进入并按下enter),我添加了延迟到API和调用exec数组。是这样的:

terminal.exec('command').then(function() {
   terminal.echo('command finished');
});

or

terminal.exec(['command 1', 'command 2', 'command 3']).then(function() {
   terminal.echo('all commands finished');
});

命令可以运行异步代码,exec需要按顺序调用用户代码。我的第一个api使用暂停/恢复调用对,在新的api中,当用户返回承诺时,我调用那些自动。所以用户代码可以直接使用

return $.get('/some/url');

or

var d = new $.Deferred();
setTimeout(function() {
    d.resolve("Hello Deferred"); // resolve value will be echoed
}, 500);
return d.promise();

我使用这样的代码:

exec: function(command, silent, deferred) {
    var d;
    if ($.isArray(command)) {
        return $.when.apply($, $.map(command, function(command) {
            return self.exec(command, silent);
        }));
    }
    // both commands executed here (resume will call Term::exec)
    if (paused) {
        // delay command multiple time
        d = deferred || new $.Deferred();
        dalyed_commands.push([command, silent, d]);
        return d.promise();
    } else {
        // commands may return promise from user code
        // it will resolve exec promise when user promise
        // is resolved
        var ret = commands(command, silent, true, deferred);
        if (!ret) {
            if (deferred) {
                deferred.resolve(self);
                return deferred.promise();
            } else {
                d = new $.Deferred();
                ret = d.promise();
                ret.resolve();
            }
        }
        return ret;
    }
},

Dalyed_commands用于恢复函数,使用所有的Dalyed_commands再次调用exec。

和部分命令函数(我已经剥离了不相关的部分)

function commands(command, silent, exec, deferred) {

    var position = lines.length-1;
    // Call user interpreter function
    var result = interpreter.interpreter(command, self);
    // user code can return a promise
    if (result != undefined) {
        // new API - auto pause/resume when using promises
        self.pause();
        return $.when(result).then(function(result) {
            // don't echo result if user echo something
            if (result && position === lines.length-1) {
                display_object(result);
            }
            // resolve promise from exec. This will fire
            // code if used terminal::exec('command').then
            if (deferred) {
                deferred.resolve();
            }
            self.resume();
        });
    }
    // this is old API
    // if command call pause - wait until resume
    if (paused) {
        self.bind('resume.command', function() {
            // exec with resume/pause in user code
            if (deferred) {
                deferred.resolve();
            }
            self.unbind('resume.command');
        });
    } else {
        // this should not happen
        if (deferred) {
            deferred.resolve();
        }
    }
}