I have multiple Javascript functions, each does some DOM manipulation and then runs an ajax request. I want to be able to run the first function, which manipulates the DOM and then fires its ajax request, then, when the ajax request finishes, I want to run the second function if the ajax request returned true, or stop execution of the rest of the functions and do some other DOM manipulation (like showing an error message).
I want these functions to run sequentially, one after the other, and only keep going if none of them returns false from their ajax request. If none of them returns false, then all of them should run and eventually I would do some manipulation I have inside my "always" callback.
How can I accomplish this?
My first thought was to use promises, to keep the code cleaner, but after several hours of reading I just can't get how to make this work.
Below is my current code, and this is what I get in my console when it executes:
inside test1
inside test2
inside test3
fail
[arguments variable from fail method]
always
[arguments variable from always method]
This is what I want to get in my console (note the missing "inside test3" string):
inside test1
inside test2
fail
[arguments variable from fail method]
always
[arguments variable from always method]
Here's the code:
(function($) {
var tasks = {
init: function() {
$.when(
this.test1(),
this.test2(),
this.test3()
).done(function() {
console.log('done');
console.log(arguments);
})
.fail(function() {
console.log('fail');
console.log(arguments);
})
.always(function() {
console.log('always');
console.log(arguments);
});
},
test1: function() {
console.log('inside test1');
return $.getJSON('https://baconipsum.com/api/?type=meat-and-filler');
},
test2: function() {
console.log('inside test2');
// note the misspelled "typ" arg to make it fail and stop execution of test3()
return $.getJSON('https://baconipsum.com/api/?typ=meat-and-filler');
},
test3: function() {
console.log('inside test3');
return $.getJSON('https://baconipsum.com/api/?type=meat-and-filler');
}
};
tasks.init();
})(jQuery)
Any ideas?
Using promises does make this code far cleaner
Note: Promise/A+ promise .then and .catch callbacks only ever take one argument, so no need for looking at arguments
, just use a single argument
This code (ES2015+) shows how easy
let p = Promise.resolve();
Promise.all([this.test1, this.test2, this.test3].map(fn => p = p.then(fn()))).then(allResults =>
alternatively, using array.reduce
[this.fn1, this.fn2, this.fn3].reduce((promise, fn) => promise.then(results => fn().then(result => results.concat(result))), Promise.resolve([])).then(allResults =>
In both cases, allResults
is an array of (resolved) results from the testN
functions
It creates a (resolved) promise to "start" the chain of promises
the Array#map chains each function (this.test1 etc) to be executed in .then of the previous result. The results of each this.testn
promise are returned in a new array, which is the argument for Promise.all
if any of testN fail, the next wont be executed
var tasks = {
init () {
let p = Promise.resolve();
Promise.all([this.test1, this.test2, this.test3].map(fn => p = p.then(fn())))
.then(results => {
console.log('done');
console.log(results);
return results; // pass results to next .then
}).catch(reason => {
console.log('fail');
console.log(reason);
return reason; // because I return rather than throw (or return a Promise.reject),
//the next .then can will get `reason` in it's argument
}).then(result => {
console.log('always');
console.log(result);
});
},
test1() {
return $.getJSON('https://baconipsum.com/api/?type=meat-and-filler');
},
test2() {
return $.getJSON('https://baconipsum.com/api/?typ=meat-and-filler');
},
test3() {
return $.getJSON('https://baconipsum.com/api/?type=meat-and-filler').then(result => {
if(someCondition) {
throw new Error("sum ting wong");
// or
return Promise.reject(new Error("sum ting wong"));
}
return result;
});
}
};
tasks.init();
for the code in the question, you could simplify it further
var tasks = {
init () {
let p = Promise.resolve();
const urls = [
'https://baconipsum.com/api/?type=meat-and-filler',
'https://baconipsum.com/api/?typ=meat-and-filler',
'https://baconipsum.com/api/?type=meat-and-filler'
];
Promise.all(urls.map(url => p = p.then(() => $.getJSON(url))))
.then(results => {
console.log('done');
console.log(results);
return results; // pass results to next .then
}).catch(reason => {
console.log('fail');
console.log(reason);
return reason; // because I return rather than throw (or return a Promise.reject),
//the next .then can will get `reason` in it's argument
}).then(result => {
console.log('always');
console.log(result);
});
}
};
tasks.init();