如何使用jQuery延迟?


279

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的示例源是作弊。

当我们抽象出一个操作是同步还是异步完成时,我对什么技术可用特别感兴趣。


19
在常见问题解答中:避免在任何...同样有效的地方提出主观问题:“您最喜欢的______是什么?” (其重点)
TJ Crowder

2
@TJCrowser我将看看重新措词。
雷诺斯

5
这是一个很好的问题,但不能有许多人谁可以回答:-)
尖尖的

2
@Pointy我主要看看那些是第三方插件的人。并鼓励人们坐下来使用它!
雷诺斯

1
._Deferred只是使用的真正的“延迟对象” .Deferred。这是一个内部对象,您很可能永远不需要。
大卫·唐

Answers:


212

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

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请求将获取数据并将其添加到缓存中。的$.when/ .then不关心任何的这一点; 您需要关心的就是使用响应,.then()在两种情况下都将响应传递给处理程序。jQuery.when()手柄的非无极/延迟作为一个已完成,立即执行任何.done().then()上链。

对于任务可能会异步执行或可能不会异步执行以及您希望从代码中抽象出该条件的情况,Deferreds非常适合。

使用$.when助手的另一个真实示例:

$.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

});

4
两个辉煌的例子。我实现了与第二个类似的东西,但是有4个ajax请求,并且除了可读性,紧凑性,逻辑性,可维护性等之外,它还表现出色。jQuery.Deferred是真正的好东西。
PJP

5
这是有关此主题的有用视频bigbinary.com/videos/3-using-deferred-in-jquery
Nick Vanderbilt

5
如果结果是虚假的,则缓存将不起作用。我也不喜欢getData根据所采用的分支返回2种不同类型的事实。
Marko Dumic

3
有关更好的ajax缓存实现,请参见下面的Julian D.的答案。
event_jr 2012年

1
我不了解第一个代码示例的工作原理:我不了解对象未缓存的情况,但是如果对象不缓存,则不会cache[ val ]返回诺言(jQuery文档说该参数是发送者返回的数据),这意味着的成员访问权限.then会出错...对吗?我想念什么?
chacham15 2014年

79

这与ehynd的答案中 AJAX缓存的实现略有不同。

正如在fortuneRice的后续问题中所述,如果在一个请求返回之前执行了请求,那么ehynd的实现实际上并没有阻止多个相同的请求。那是,

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

如果“ xxx”的结果之前尚未缓存,则很可能会导致3个AJAX请求。

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

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.
});

1
我认为这仍然不是完美的,因为您永远不会在第一次获取时就清除/更新缓存。这将使AJAX调用无法进行任何更新。
zyzyis 2014年

45

可以使用延期代替互斥。这基本上与多种ajax使用方案相同。

MUTEX

var mutex = 2;

setTimeout(function() {
 callback();
}, 800);

setTimeout(function() {
 callback();
}, 500);

function callback() {
 if (--mutex === 0) {
  //run code
 }
}

延期

function timeout(x) {
 var dfd = jQuery.Deferred();
 setTimeout(function() {
  dfd.resolve();
 }, x);
 return dfd.promise();
}

jQuery.when(
timeout(800), timeout(500)).done(function() {
 // run code
});

当仅将Deferred用作互斥量时,请注意性能影响(http://jsperf.com/deferred-vs-mutex/2)。尽管Deferred带来的便利以及其他好处是非常值得的,并且在实际(基于用户驱动的事件)使用中,性能影响应该不明显。


我很难找到这个。我在包含setInterval的函数上使用了该函数,一旦div的宽度超过一定数量,该函数将返回已解析的promise并自毁。如果我无法解决问题,这是用于故障排除和解决方案,但对此我感到欣喜若狂。
JSG


20

我一直致力于的另一用途是从多个来源获取数据。在下面的示例中,我将获取现有应用程序中使用的多个独立的JSON模式对象,以在客户端和REST服务器之间进行验证。在这种情况下,我不希望浏览器端应用程序在加载所有模式之前就开始加载数据。$ .when.apply()。then()非常适合此操作。感谢Raynos提供有关使用then(fn1,fn2)监视错误情况的指针。

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);
});     

10

使用Deferreds为任何类型的计算(通常是一些性能密集型或长期运行的任务)实现缓存的另一个示例:

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中使用以上代码。


9

1)使用它来确保有序执行回调:

var step1 = new Deferred();
var step2 = new Deferred().done(function() { return step1 });
var step3 = new Deferred().done(function() { return step2 });

step1.done(function() { alert("Step 1") });
step2.done(function() { alert("Step 2") });
step3.done(function() { alert("All done") });
//now the 3 alerts will also be fired in order of 1,2,3
//no matter which Deferred gets resolved first.

step2.resolve();
step3.resolve();
step1.resolve();

2)使用它来验证应用程序的状态:

var loggedIn = logUserInNow(); //deferred
var databaseReady = openDatabaseNow(); //deferred

jQuery.when(loggedIn, databaseReady).then(function() {
  //do something
});

2

您可以使用延迟的对象进行流畅的设计,使其在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);
});

这将序列化您的代码执行,以使其按预期执行。当将对象方法作为延迟的回调传递时,要当心陷阱。一旦将这种方法作为延迟的回调执行,则“ this”引用将被引用延迟的对象,并且不再引用该方法所属的对象。


如何进行序列化?您已经解决了队列,因此resizeQueue.done(resizeAlgorithm)与完全相同resizeAlgorithm。完全是假的!
雷诺斯2011年

当resizeAlgorithm的代码很复杂时,当您为调整窗口大小的每个像素调用该函数时,webkit中的JavaScript实现将失去同步。Deferred将您的回调保留在队列中,并以FIFO顺序执行它们。因此,如果您添加了一个“完成”回调并且由于已解决了延迟而立即执行,则在第一个回调仍在执行时添加到该延迟中的另一个“完成”回调将被添加到队列中,并且必须等待返回的第一个回调。我希望这回答了你的问题。
米洛什RASIC

浏览器中的JS解释器是单线程的。除非您的resizeAlgorithm内有一些异步代码,否则整个函数应在下一次调用之前完成操作.done
雷诺斯2011年

@Raynos:我知道这一点,但是我试图简单地在resize上调用resizeAlgorithm,它在webkit浏览器中提供了一个空白页面,同时在其他浏览器中也可以正常工作。推迟解决了这个问题。我没有足够的时间对此进行更深入的研究。可能是webkit的错误。我认为,如果resizeAlgorithm具有一些异步代码,则在我的示例中使用的延迟不会有所帮助。
米洛什·拉希奇(MilošRašić)2011年

2
您不应该使用节流阀/去抖动插件benalman.com/projects/jquery-throttle-debounce-plugin之类的东西来防止每次调整大小时函数触发更多tahn的问题。
wheresrhys 2011年

2

您也可以将其与任何使用JQuery的第三方库集成。

一个这样的库是Backbone,它实际上将在其下一版本中支持Deferred。


2
用于read more here代替on my blog。这是一种更好的做法,可以(不小心)使您免受垃圾邮件的困扰。:)
Lokesh Mehra 2012年

1

我只是在实际代码中使用了Deferred。在jQuery Terminal项目中,我具有函数exec来调用用户定义的命令(就像他正在输入它并按enter一样),我已将Deferreds添加到API并使用数组调用exec。像这样:

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

要么

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

这些命令可以运行异步代码,而exec需要按顺序调用用户代码。我的第一个api使用一对暂停/恢复调用,在新的API中,当用户返回诺言时,我会自动调用它们。因此用户代码只能使用

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

要么

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();
        }
    }
}

1

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

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.
});

朱利安·D(Julian D.)的答案将是正确的,并且是更好的解决方案。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.