A Deferred object is a mechanism for referencing a value which may (or may not) be available at a later point.
Every so often you might require a group of operations to complete before you continue. These operations might need to happen either in sequence or in any order, but they all have to finish before the code can continue.
Using deferred objects for Ajax calls
A popular example of a deferred object is making multiple concurrent ajax calls and then using the data returned after all requests have completed.
ajax1 = $.get("/file1.json")
ajax2 = $.get("/file2.json")
$.when(ajax1, ajax2).then (response1, response2) ->
# ready to continue with data from both sources
The code examples in this article are written in CoffeeScript which compiles down to JavaScript.
The intent of this code reads clearly. When the two ajax calls have finished loading, execute the anonymous function.
The $.when
and deferred.then
functions are part of the jQuery Deferred API. Also of note, $.get
returns a jqXHR object, which implements the promise interface. That's the reason we can use it as a parameter in the $.when
function.
In the example above, the two ajax calls can occur independently. Sometimes, however, you might need the response from the first ajax call before you can continue with the second operation. Normally, you could create a callback chain like the one below:
$.get
url: "/json/source-1"
success: (response1) ->
$.get
url: "/json/source-2"
data: { someVar: response1.someVal }
success: (response2) ->
$.get
url: "/json/source-3"
data: { someVar: response2.someVal }
success: (response3) ->
// finally do something here
A more elegant solution is to use a when/then chain which makes the code appear less cluttered:
ajax1 = $.get({
url: "/json/source-1"
})
ajax2 = (response1) => {
return $.get({
url: "/json/source-2",
data: { someVar: response1.someVal }
})
}
ajax3 = (response2) => {
return $.get({
url: "/json/source-3",
data: { someVar: response2.someVal }
})
}
chain = $.when(ajax1).then(ajax2).then(ajax3)
chain.done((response3) => {
// finally do something here
})
In the example above ajax1
is a jqXHR object whereas ajax2
and ajax3
are functions that return a jqXHR object. The distinction is important. The deferred.then
function expects a callback which can return a promise. If any of the ajax requests fail, the chain.done
callback will never be triggered, just as you'd expect.
Usefulness aside from Ajax requests
Ajax requests are the best examples of Deferred Objects because they're so commonly used. But some other cases for Deferred Objects include file access, web workers or other asynchronous calls.
An interesting use I've toyed with is user interaction. Embedded below is an example that shows how to combine deferred objects with mouse events to create a simple mouse dexterity game.
Code examples
Play the game in a new window or view the source on Github.
In the game above, all of the squares have a deferred object attached to their mouse over event. The player wins if they can trigger all of the red squares before mousing over any grey squares.
Take a look at the source and specs for this game on Github if you want to learn more.
How does this work?
I use Deferred objects in two parts.
# source:
# src/javascripts/game.js.coffee:30
$.when(safePiecesMousedOver...).then =>
@handleGameover true
_.each safePiecesMousedOver, (piece) =>
pieceMousedOver = piece.promise()
$.when(pieceMousedOver).then (evt) =>
target = evt.currentTarget
$(target).css("opacity", "1")
unless @running or @gameover
$("img").hide()
@running = true
@startTimer()
_.each nonSafePiecesMousedOver, (piece) =>
pieceMousedOver = piece.promise()
$.when(pieceMousedOver).then =>
@handleGameover false
@reset()
In this set of calls I attach callbacks for three cases: All safe (red) pieces being moused over, any red piece moused over, and any non-safe (grey) piece moused over. I do various actions based on any of those three events.
Another section where I use Deferred objects is in the Timer
class. It is a small class so I've included it below.
# source:
# src/javascripts/timer.js.coffee
class window.Timer
constructor: ->
@reset()
tick: =>
currentTime = +(new Date())
@elapsedTime = currentTime - @startTime
@deferred.notify(@elapsedTime)
start: ->
unless @timer
@elapsedTime = 0
@startTime = +(new Date())
@timer = setInterval(@tick, 10)
stop: ->
@deferred.resolve()
clearTimeout(@timer)
@timer = null
reset: ->
@deferred = jQuery.Deferred()
@deferred.progress(@cb)
progress: (@cb) ->
@deferred.progress(@cb)
When you create a new Timer, you can attach a listener for its progress. This listener will then invoke your callback every 10 milliseconds with the elapsed time. This is how the timer on the game works. My progress callback updates the view with your current time.
Resources
If you'd like to find more information about Deferred objects or other forms of asynchronous control flows, check out the articles below.