How can jQuery deferred be used?
Asked Answered
R

11

284

jQuery 1.5 brings the new Deferred object and the attached methods .when, .Deferred and ._Deferred.

For those who haven't used .Deferred before, I've annotated the source for it.

What are the possible usages of these new methods, how do we go about fitting them into patterns?

I have already read the API and the source, so I know what it does. My question is how can we use these new features in everyday code?

I have a simple example of a buffer class that calls AJAX requests in order. (Next one starts after the previous one finishes).

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

I'm looking for demonstrations and possible uses of .Deferred and .when.

It would also be lovely to see examples of ._Deferred.

Linking to the new jQuery.ajax source for examples is cheating.

I am particularly interested in what techniques are available when we abstract away whether an operation is synchronously or asynchronously done.

Repudiation answered 2/2, 2011 at 0:36 Comment(9)
From the FAQ: avoid asking subjective questions where...every answer is equally valid: “What’s your favorite ______?” (their emphasis)Softhearted
@T.J.Crowser I'll look at rewording it.Repudiation
It's a good question but there can't be that many people who can answer :-)Irremeable
@Irremeable I mainly looking at those who used it when it was a 3rd party plugin. And encouraging people to sit down and use it!Repudiation
._Deferred is simply the true "Deferred object" which .Deferred uses. It's an internal object which you'll most likely never need.Obvert
@Box9 I know that. It's used internally but it's made publicly accessible. Which means people can use it, the only difference is the ability to cancel it, I don't know what clever things we can do with that extra cancel function. The fact you can cancel is useful. The only reference I've seen is mapping n deferred objects into one deferred object and cancelling all when one triggers.Repudiation
isResolved is and/or will be deprecated in the near future.Eternity
How this is used? could you put an example to demonstrate how to call it?Barrybarrymore
I found this pretty useful jqfundamentals.com/chapter/ajax-deferredsBullfight
A
216

The best use case I can think of is in caching AJAX responses. Here's a modified example from Rebecca Murphey's intro post on the topic:

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

Basically, if the value has already been requested once before it's returned immediately from the cache. Otherwise, an AJAX request fetches the data and adds it to the cache. The $.when/.then doesn't care about any of this; all you need to be concerned about is using the response, which is passed to the .then() handler in both cases. jQuery.when() handles a non-Promise/Deferred as a Completed one, immediately executing any .done() or .then() on the chain.

Deferreds are perfect for when the task may or may not operate asynchronously, and you want to abstract that condition out of the code.

Another real world example using the $.when helper:

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

});
Antipope answered 2/2, 2011 at 12:57 Comment(12)
It's defiantly good to to use $.when().then to abstract away whether the data is asynchronously or synchronolously handled. The buffer does a similar thing except it abstracts away whether a task is handled sychronously or asynchronously.Repudiation
Two brilliants examples. I implemented something similar to the 2nd one, but with 4 ajax requests, and it performs well, in addition to be far more legible, compact, logic, maintainable, etc. jQuery.Deferred is a real good thing.Lissotrichous
Here is a useful video on this topic bigbinary.com/videos/3-using-deferred-in-jqueryGilboa
Caching will not work if the result is falsy value. Also I don't like the fact getData returns 2 different types depending on the branch taken.Hyperborean
See Julian D.'s answer below for a better implementation of ajax caching.Taoism
Link from @NickVanderbilt is inactive. Valide is blog.bigbinary.com/2011/09/03/jquery-deferred.htmlRefund
Is $.when necessary in the first snippet? I believe .then acts on deferreds, and getData already returns one.Aftershaft
@pimvdb, it might not always return a deferred. The first part of the if statement pulls an object from the cache, not returning the $.ajax object, but a generic JS one, that may or may not (most likely not) be a deferred. As such, $.when is needed because it handles these cases. Anything that is passed to $.when that is not a deferred object is treated like a completed deferred.Vauntcourier
this examples somehow remind me of requirejs. wondering if they have similar implementations...Hippodrome
I dont understand how the first code example even works: I understand the case where the object is not cached, but if it is then wont cache[ val ] NOT return a promise (the jquery documentation says that the parameter is the data returned by the sender) meaning that the member access of .then will error...right? What am I missing?Thynne
Edit dated Jul 28 '14 should be undone. $.when(...) wrapper is necessary to cater for the case that the non-thenable cached value is returned.Latifundium
chacham15 agree - the edit broke it. roamer-1888 undoing that edit now.Coagulant
A
79

Here is a slightly different implementation of an AJAX cache as in ehynd's answer.

As noted in fortuneRice's follow-up question, ehynd's implementation didn't actually prevent multiple identical requests if the requests were performed before one of them had returned. That is,

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

will most likely result in 3 AJAX requests if the result for "xxx" has not already been cached before.

This can be solved by caching the request's Deferreds instead of the result:

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.
});
Advert answered 22/1, 2012 at 11:3 Comment(1)
I think this is still not perfect, since you never clear / update the cache once the first time fetched. This will make AJAX call not working for any update.Reactor
B
46

A deferred can be used in place of a mutex. This is essentially the same as the multiple ajax usage scenarios.

MUTEX

var mutex = 2;

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

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

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

DEFERRED

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

When using a Deferred as a mutex only, watch out for performance impacts (http://jsperf.com/deferred-vs-mutex/2). Though the convenience, as well as additional benefits supplied by a Deferred is well worth it, and in actual (user driven event based) usage the performance impact should not be noticeable.

Bluebell answered 23/5, 2011 at 18:44 Comment(1)
It was surprisingly difficult for me to find this. I used it on a function containing a setInterval which would return the resolved promise and self destructed once the width of div made it over a certain number. It was for troubleshooting and a solution if I couldn't solve my issue, but I am ecstatic about it.Rowlandson
S
29

This is a self-promotional answer, but I spent a few months researching this and presented the results at jQuery Conference San Francisco 2012.

Here is a free video of the talk:

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

Supersaturated answered 10/10, 2012 at 22:18 Comment(0)
A
20

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);
});     
Alps answered 4/2, 2011 at 4:54 Comment(0)
A
10

Another example using Deferreds to implement a cache for any kind of computation (typically some performance-intensive or long-running tasks):

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

Here is an example of using this class to perform some (simulated heavy) calculation:

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

The same underlying cache could be used to cache Ajax requests:

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

You can play with the above code in this jsFiddle.

Advert answered 22/1, 2012 at 14:14 Comment(0)
T
9

1) Use it to ensure an ordered execution of callbacks:

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) Use it to verify the status of the app:

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

jQuery.when(loggedIn, databaseReady).then(function() {
  //do something
});
Tandi answered 14/9, 2012 at 6:47 Comment(0)
G
2

You can use a deferred object to make a fluid design that works well in webkit browsers. Webkit browsers will fire resize event for each pixel the window is resized, unlike FF and IE which fire the event only once for each resize. As a result, you have no control over the order in which the functions bound to your window resize event will execute. Something like this solves the problem:

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 will serialize the execution of your code so that it executes as you intended it to. Beware of pitfalls when passing object methods as callbacks to a deferred. Once such method is executed as a callback to deferred, the 'this' reference will be overwritten with reference to the deferred object and will no longer refer to the object the method belongs to.

Gift answered 1/3, 2011 at 12:27 Comment(5)
How does this do any serialization? You've already resolved the queue so resizeQueue.done(resizeAlgorithm) is the exact same as resizeAlgorithm. It's a complete sham!Repudiation
When the code of your resizeAlgorithm is complex, JavaScript implementation in webkit will loose synchronization when the function is called for each pixel you resize the window. Deferred keeps your callbacks in a queue and executes them in a FIFO order. So, if you add a 'done' callback and it executes immediately because the deferred is already resolved, another 'done' callback that is added to the deferred while the first callback is still executing will be added to the queue and will have to wait for the first callback to return. I hope this answers your question.Welloiled
the JS interpreter in the browser is single threaded. Unless your resizeAlgorithm has some async code inside it the entire function should have finished operating before the next call to .done is made.Repudiation
@Raynos: I'm aware of that, but I tried to simply call the resizeAlgorithm on resize and it gives a blank white page in webkit browsers while working perfectly in others. The deferred solves this problem. I haven't had enough time to do some deeper research into this. Might be a webkit bug. I don't think the deferred as used in my example would help if resizeAlgorithm had some asynchronous code.Welloiled
Shouldn't you be using something like the throttle/debounce plugin benalman.com/projects/jquery-throttle-debounce-plugin to prevent your functions firing more tahn once per resize.Edie
A
2

You can also integrate it with any 3rd-party libraries which makes use of JQuery.

One such library is Backbone, which is actually going to support Deferred in their next version.

Accustomed answered 15/5, 2011 at 7:0 Comment(1)
Use read more here in place of on my blog. Its a better practice and can save you answer from (accidently) being spammed. :)Religieux
S
1

I've just used Deferred in real code. In project jQuery Terminal I have function exec that call commands defined by user (like he was entering it and pressing enter), I've added Deferreds to the API and call exec with arrays. like this:

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

the commands can run async code, and exec need to call user code in order. My first api use pair of pause/resume calls and in new API I call those automatic when user return promise. So user code can just use

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

or

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

I use code like this:

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 is used in resume function that call exec again with all dalyed_commands.

and part of the commands function (I've stripped not related parts)

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();
        }
    }
}
Smell answered 2/6, 2014 at 7:34 Comment(0)
C
1

The answer by ehynds will not work, because it caches the responses data. It should cache the jqXHR which is also a Promise. Here is the correct code:

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

The answer by Julian D. will work correct and is a better solution.

Colour answered 28/1, 2015 at 7:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.