Almost all of the functions in my program have some sort of asynchronous call, but they all rely on some previous function's results. Because of that, I've hard-coded the next function call into each individual one as such:
function getStuff() {
$.ajax({
...
success: function(results) {
// other functions involving results
getMoreStuff(results);
}
});
}
function getMoreStuff(results) {
$.ajax({
...
success: function(moreResults) {
// other functions involving moreResults
doSomethingWithStuff(moreResults);
}
);
}
And so on. It's a large chain where each function calls the next. While this works within the program, it makes each function useless individually.
I'm a bit lost on how to avoid this problem. I couldn't figure out how to use general callback functions, because when I make the function calls, it ends up like this (using the functions above):
getStuff(function() {
getMoreStuff(results, doSomethingWithStuff);
};
But then 'results' hasn't been defined yet.
The solution seems obvious, I'm just being a bit dense about it. Sorry!
You have a couple of choices. You can have your code using those functions look like this, using callbacks:
getStuff(function(results) {
getMoreStuff(results, doSomethingWithStuff);
});
or like this, using jQuery's Deferred
and Promise
objects:
getStuff().then(getMoreStuff).then(doSomethingWithStuff):
Have both getStuff
and getMoreStuff
accept an argument that is a callback to call when they're done, e.g.:
function getStuff(callback) {
// ^------------------------------ callback argument
$.ajax({
...
success: function(results) {
// other functions involving results
callback(results);
// ^------------------------------------ use the callback arg
}
});
}
...and similarly for getMoreStuff
.
Deferred
and Promise
jQuery's ajax
function integrates with its Deferred
and Promise
features. You can just add return
to your existing functions to make that work, e.g.:
function getStuff(callback) {
return $.ajax({
...
});
}
(Note: No need for the success
callback.)
Then this code:
getStuff().then(getMoreStuff).then(doSomethingWithStuff);
does this:
getStuff
starts its ajax
call and returns the Promise
that call creates.
When that ajax
call completes and resolves the promise, getMoreStuff
is called with the results of the ajax
call as its first argument. It starts its ajax
call.
When getMoreStuff
's ajax
call completes, doSomethingWithStuff
is called with the results of that call (the one in getMoreStuff
).
It's important to use then
, not done
, in order to get the correct results passed on at each stage. (If you use done
, both getMoreStuff
and doSomethingWithStuff
will see the results of getStuff
's ajax
call.)
Here's a full example using ajax
:
Fiddle | Alternate Fiddle with the ajax
calls taking one second each (makes it easier to see what's happening)
function getStuff() {
display("getStuff starting ajax")
return $.ajax({
url: "/echo/json/",
type: "POST",
data: {json: '{"message": "data from first request"}'},
dataType: "json"
});
}
function getMoreStuff(results) {
display("getMoreStuff got " + results.message + ", starting ajax");
return $.ajax({
url: "/echo/json/",
type: "POST",
data: {json: '{"message": "data from second request"}'},
dataType: "json"
});
}
function doSomethingWithStuff(results) {
display("doSomethingWithStuff got " + results.message);
}
getStuff().then(getMoreStuff).then(doSomethingWithStuff);
function display(msg) {
var p = document.createElement('p');
p.innerHTML = String(msg);
document.body.appendChild(p);
}
Output:
getStuff starting ajax getMoreStuff got data from first request, starting ajax doSomethingWithStuff got data from second request
You don't need to be using ajax
to get the benefit of this, you can use your own Deferred
and Promise
objects, which lets you write chains like this:
one().then(two).then(three);
...for any situation where you may have asynchronous completions.
Here's a non-ajax
example:
Fiddle
function one() {
var d = new $.Deferred();
display("one running");
setTimeout(function() {
display("one resolving");
d.resolve("one");
}, 1000);
return d.promise();
}
function two(arg) {
var d = new $.Deferred();
display("Two: Got '" + arg + "'");
setTimeout(function() {
display("two resolving");
d.resolve("two");
}, 500);
return d.promise();
}
function three(arg) {
var d = new $.Deferred();
display("Three: Got '" + arg + "'");
setTimeout(function() {
display("three resolving");
d.resolve("three");
}, 500);
return d.promise();
}
one().then(two).then(three);
function display(msg) {
var p = document.createElement('p');
p.innerHTML = String(msg);
document.body.appendChild(p);
}
Output:
one running one resolving Two: Got 'one' two resolving Three: Got 'two' three resolving
These two (the ajax
example and the non-ajax
example) can be combined when necessary. For instance, if we take getStuff
from the ajax
example and we decide we have to do some processing on the data before we hand it off to getMoreStuff
, we'd change it like this: Fiddle
function getStuff() {
// Create our own Deferred
var d = new $.Deferred();
display("getStuff starting ajax")
$.ajax({
url: "/echo/json/",
type: "POST",
data: {json: '{"message": "data from first request"}', delay: 1},
dataType: "json",
success: function(data) {
// Modify the data
data.message = "MODIFIED " + data.message;
// Resolve with the modified data
d.resolve(data);
}
});
return d;
}
Note that how we use that didn't change:
getStuff().then(getMoreStuff).then(doSomethingWithStuff);
All that changed was within getStuff
.
This is one of the great things about the whole "promise" concept (which isn't at all specific to jQuery, but jQuery gives us handy versions to use), it's fantastic for decoupling things.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With