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源代码的例子是作弊。

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


当前回答

您可以使用延迟对象来实现在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对象的引用,并且不再引用该方法所属的对象。

其他回答

我能想到的最佳用例是缓存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

});

Another use that I've been putting to good purpose is fetching data from multiple sources. In the example below, I'm fetching multiple, independent JSON schema objects used in an existing application for validation between a client and a REST server. In this case, I don't want the browser-side application to start loading data before it has all the schemas loaded. $.when.apply().then() is perfect for this. Thank to Raynos for pointers on using then(fn1, fn2) to monitor for error conditions.

fetch_sources = function (schema_urls) {
    var fetch_one = function (url) {
            return $.ajax({
                url: url,
                data: {},
                contentType: "application/json; charset=utf-8",
                dataType: "json",
            });
        }
    return $.map(schema_urls, fetch_one);
}

var promises = fetch_sources(data['schemas']);
$.when.apply(null, promises).then(

function () {
    var schemas = $.map(arguments, function (a) {
        return a[0]
    });
    start_application(schemas);
}, function () {
    console.log("FAIL", this, arguments);
});     

ehyds的答案将不起作用,因为它缓存了响应数据。它应该缓存jqXHR,这也是一个Promise。 下面是正确的代码:

var cache = {};

function getData( val ){

    // return either the cached value or an
    // jqXHR object (which contains a promise)
    return cache[ val ] || $.ajax('/foo/', {
        data: { value: val },
        dataType: 'json',
        success: function(data, textStatus, jqXHR){
            cache[ val ] = jqXHR;
        }
    });
}

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

Julian d的答案是正确的,是一个更好的解决方案。

您可以使用延迟对象来实现在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对象的引用,并且不再引用该方法所属的对象。

我只是在实际代码中使用了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();
        }
    }
}