Basically, i have to 2 functions A and B to get data from rest, then I want to run function C after successfully get the data
this is my code :
JS:
var A = function() {
$.getJSON('linkA', function(data) {
a = data.total;
console.log("A : " + a);
});
return new Promise(function(resolve, reject) {
resolve('Got a');
});
};
var B = function() {
$.getJSON('linkB', function(data) {
b = data.total;
console.log("B:" + b);
});
return new Promise(function(resolve, reject) {
resolve('Got b');
});
};
function run() {
Promise.all([A(), B()]).then(function() {
console.log("Got A and B")
});
}
HTML:
<script>
run();
</script>
I want the result should be in Console :
A: //data
B: //data
Got A and B
However , i still got "Got A and B" before the other two lines . I guess because getting data takes quite a long time so the program write the "Got A and B" first. But there is must be someway to get the goal right ??
You're immediately resolving the promises, rather than waiting until you get the data back.
You're also falling prey to the promise creation anti-pattern. $.getJSON already gives you a thenable*, so just use it and use then to transform the data (by accessing total):
var A = function() {
return $.getJSON('linkA').then(function(data) {
return data.total;
});
};
And the same with B. Then your Promise.all code will work, and if you tweak it you can actually get a and b:
function run() {
Promise.all([A(), B()]).then(function(results) {
var a = results[0], b = results[1];
// ...use `a` and `b`...
});
}
Or if you can rely on ES2015+ features, you can use an arrow function and destructuring (or a normal function and destructuring if you prefer):
function run() {
Promise.all([A(), B()]).then(([a, b]) => {
// ...use `a` and `b`...
});
}
Since the rest of your code just relies on jQuery, not Promise support in the browser, you could also use jQuery's $.when:
function run() {
$.when(A(), B()).then(function(a, b) {
// ...use `a` and `b`...
});
}
If you needed to explicitly do new Promise (you don't here) to interface with a non-thenable API, it would look like this:
// YOU DON'T NEED TO DO THIS WITH $.getJSON, but with non-thenable APIs you might
var A = function() {
return new Promise(function(resolve, reject) {
$.getJSON('linkA', function(data) {
resolve(data.total);
}).fail(function(jqXHR, textStatus, errorThrown) {
reject(errorThrown || new Error(textStatus));
});
});
};
* "thenable" - A promise-like thing, see the Promises/A+ spec.
var url = 'https://jsonplaceholder.typicode.com/posts/';
Promise.all( [url + 1, url + 2 ].map( $.get ) )
.then(
console.log
)
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Related
This question already has an answer here:
How to chain and share prior results with Promises [duplicate]
(1 answer)
Closed 5 years ago.
UPDATE: I FOUND AN "HOMEMADE" SOLUTION BY MY OWN, SCROLL DOWN TO THIS THIS SOLUTION!
When I'm doing NodeJS promises "manually" I have the option to pass the resolve value from the parent promise to it's child, or more precisely,
from the first promise to the second and so for and so on.
So, after first promise is done, I can continue using it's resolved data in the next promise. Sometimes this data is crucial for the whole process.
Such as sending a SMS via remote API, if the SMS will not be sent, I cannot be able to manage the next function.
I have tried the "shortcut" (*sorry if it does not meant to be a shortcut):
Promise.all.
Well it's nice, and it pops up the catched promise reject value first and if everything is okay it goes to finish.
But how can I preserve the resolved data in the same manner I have described above?
I will push here some code, so we can understand each other better:
Trying to do this using Promise.all
function test1() {
return new Promise(function(resolve, reject) {
resolve("test1");
});
}
function test2() {
return new Promise(function(resolve, reject) {
resolve("test2");
});
}
function test3() {
return new Promise(function(resolve, reject) {
resolve("test3");
});
}
Promise.all([test1, test2, test3].map(func => func())).then((result) => {
console.log('result: ', result);
}).catch((result) => {
console.log("Catched: ", result);
});
Trying to do this using the "manual" way:
function test1() {
return new Promise(function(resolve, reject) {
resolve("test1");
});
}
function test2(p) {
return new Promise(function(resolve, reject) {
resolve("test2");
});
}
function test3(p) {
return new Promise(function(resolve, reject) {
resolve("test3");
});
}
test1().then(function(result) {
return test2(result);
}).then(function(result) {
return test3();
}).then(function(result) {
console.log("Finished");
});
Now for the catch section if I am using the Promise.all function with the catch call back, it will rise the first catched match.
This is not good. Since I want to first tell the user he forgot filling email, later he has to be notified whether there was a problem sending SMS. But it does not working like that.
Actually, I can do it the manual way seperate for then and seperate for catch, but it is a mess.. And it is very easy to cause mistake in the syntax that will take hours to find.
********************** UPDATE - I FOUND AN HOMEMADE SOLUTIN!!! **********************
function test1() {
// some process return in overall as boolean
var overall = true;
if (overall)
return test2({test1: "is resolved"});
return catched("rejected on 1");
}
function test2(result) {
// some process return in overall as boolean
var overall = true;
result.test2 = "is resolved";
if (overall)
return test3(result);
return catched("rejected on 2");
}
function test3(result) {
// some process return in overall as boolean
var overall = true;
result.test3 = "is resolved";
if (overall)
return finished(result);
return catched("rejected on 3");
}
function finished(result) {
console.log(result);
}
function catched(result) {
console.log(result);
}
test1();
test1() will run first. After each of them done it goes to the next, syntax is simple, my homemade Promise.all is super simple thats the solution. Plus, if in future we want to add a middle function between test2 to test3, we should not think about adding it in the end handle.
Hmm.. probably that is it for now.
Hope it is clear to understand and help others!
THANKS :)
If your promises are all taking the result of the previous promise and returning to the next (like they do in your example), you can make the syntax much terser by doing something like this:
function test1() {
return new Promise(function(resolve, reject) {
resolve("test1");
});
}
function test2(p) {
return new Promise(function(resolve, reject) {
resolve(p + " test2");
});
}
function test3(p) {
return new Promise(function(resolve, reject) {
resolve(p + " test3");
});
}
test1()
.then(test2)
.then(test3)
.then(console.log)
First of all, the code blocks that you proposed are not equivalent.
The "manual" way that you show says, "run test1, then test2 with the value returned from test1, then run test3 with the value returned from test2.
The Promise.all way will fire all three functions, effectively, simultaneously and will not feed the return value from one to the next (promise.waterfall).
You may want to try async functions. They provide a more terse, synchronous looking script.
async function test1() {
return "test1";
}
async function test2(p) {
return `test2[${p}]`;
}
async function test3(p) {
return `test3[${p}]`;
}
(async function () {
let t1 = await test1();
console.log(`Look what I got: ${t1}`);
let t2 = await test2(t1);
console.log(`And here: ${t2}`);
let t3 = await test3(t2);
console.log(`Also here: ${t3}`);
return {
t1,
t2,
t3
};
}()).then(console.log);
Running this gives you the output:
Look what I got: test1
And here: test2[test1]
Also here: test3[test2[test1]]
{ t1: 'test1', t2: 'test2[test1]', t3: 'test3[test2[test1]]' }
In response to guest271314's comment:
Why would a "Promise chain" need to be broken?
I see the need to break the chain if you need to use an earlier resolution in a later sequence, but don't want to pass a conglomeration of all the results in the chain down. You can either nest callbacks which kind of defeats the point of Promises, or you could use async functions.
Consider:
const test1 = () => Promise.resolve("test1"),
test2 = (a) => Promise.resolve(`[${a}]test2`),
test3 = (a) => Promise.resolve(`[${a}]test3`),
test4 = (a) => Promise.resolve(`[${a}]test4`),
test5 = (a) => Promise.resolve(`[${a}]test5`),
greedy = (a, b) => Promise.resolve(`I need ${a} and ${b}`);
// Pass through
test1()
.then(test2)
.then(test3)
.then(test4)
.then(test5)
// Greedy should actually be getting the result of test1 and test5
.then(greedy)
.then(console.log);
// "Breaking" the chain
test1()
.then((a) => {
return test2(a)
.then(test3)
.then(test4)
.then(test5)
.then((b) => {
/*
* I have to nest if I want to give greedy
* the results of test1 and test5
*/
return greedy(a, b);
});
})
.then(console.log);
// Using async
(async function () {
const t1 = await test1(),
t2 = await test2(t1),
t3 = await test3(t2),
t4 = await test4(t3),
t5 = await test5(t4),
// I already have t1 and t5 populated
g = await greedy(t1, t5);
return g;
}()).then(console.log);
Prints:
I need [[[[test1]test2]test3]test4]test5 and undefined
I need test1 and [[[[test1]test2]test3]test4]test5
I need test1 and [[[[test1]test2]test3]test4]test5
I would recommend checking out the async functions zero298 recommends. For example (code not tested):
async function test1() {
return new Promise(function(resolve, reject) {
resolve("test1");
});
}
async function test2(p) {
return new Promise(function(resolve, reject) {
resolve("test2");
});
}
async function test3(p) {
return new Promise(function(resolve, reject) {
resolve("test3");
});
}
var test1 = await test1();
var test2 = await test2(test1);
var test3 = await test3();
console.log("Finished");
I'm using Array.forEach function of javascript to get a different message for each element of a list. So I'm using forEach function and I'm looking for a way to execute my callback function cb(result) when the foreach as finished to both execute .forEach and msgAfterTimeout. I read that there was something called promises but I don't really get how I can use them here.
function msgAfterTimeout (who, timeout, onDone) {
setTimeout(function () {
onDone(" Hello " + who + "!");
}, timeout);
}
var test="";
var list = [{name:"foo",surname:"bar"},{name:"Jean",surname:"dupond"}];
function dispName(cb)
{
list.forEach(function(item, index)
{
msgAfterTimeout(item.name, 200, function (msg)
{
test=msg+"\n";
});
cb(result);
});
}
dispName(function(data){
console.log(data);
});
Here's your example with Promises:
var list = [{name: "foo", surname: "bar"}, {name: "Jean", surname: "dupond"}];
function msgAfterTimeout(who, timeout) {
return new Promise(resolve =>
setTimeout(
() => resolve(" Hello " + who + "!"),
timeout)
);
}
Promise.all(
list.map(item => msgAfterTimeout(item.name, 200))
).then(
result => console.log(result.join('\n'))
);
Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
function msgAfterTimeout (who, timeout, onDone) {
setTimeout(function () {
onDone(" Hello " + who + "!");
}, timeout);
}
var test= "";
var list = [{name:"foo",surname:"bar"},{name:"Jean",surname:"dupond"}];
function newFunc(list, i, endCb) {
var name = list[i].name;
msgAfterTimeout(name, 200, function(msg) {
var test = msg + "\n";
console.log(test);
if(i+1 == list.length) {
endCb();
}
else {
newFunc(list, i+1, endCb);
}
});
}
function dispName(cb)
{
newFunc(list, 0 , cb);
}
dispName(function(){
console.log("Done with all!");
});
This outputs:
Hello foo!
Hello Jean!
Done with all!
forEach processes things in series, so for the sake of this answer, let's assume that's a requirement. Promise.all as demonstrated by #georg will process the items in parallel – it's a good answer but it won't help you if a series is what you actually need.
The Array.prototype methods reduce, map, and forEach are synchronous, but you could easily make an asynchronous variants. You're using forEach here but you're actually performing a reduce by hand. So we'll start by implementing an asynchronous reducek and then go from there
Don't worry too much about understanding reducek itself. All you need to know is the primary difference between reducek and reduce is that the reducing function receives an extra callback argument that is called when each item is done, and reducek has its own callback for when the entire input list is done.
function reducek (f, acc, [x, ...xs], k) {
if (x === undefined)
return k (acc)
else
return f (acc, x, y => reducek (f, y, xs, k))
}
function msgAfterTimeout (who, timeout, onDone) {
return setTimeout(onDone, timeout, "Hello " + who + "!")
}
function messageReducer (acc, item, done) {
return msgAfterTimeout(item.name, 200, function (text) {
return done([...acc, text])
})
}
function displayName (list, done) {
// use our async reduce here
return reducek (messageReducer, [], list, done)
}
var list = [{name:"foo",surname:"bar"},{name:"Jean",surname:"dupond"}]
displayName (list, function (texts) {
console.log("Done!", texts.join(' '))
})
// Done! Hello foo! Hello Jean!
Important things to notice ...
No longer performing reduce by hand – instead of using a test variable initialized with state of '' (empty string), reducek accepts the initial value as an argument.
Here we're using an initial state of of [] (empty array) to store each of the texts. When we're done, we join the texts together using texts.join(' ').
Not Another Ceremony ...
All of that is a lot of ceremony and continuations are not necessarily the best for asynchronous flow control. In fact, Promises were brought to JavaScript because a better tool was needed.
// instead of returning a timeout, return a Promise
function msgAfterTimeout (who, timeout) {
return new Promise(function (resolve) {
setTimeout(resolve, timeout, "Hello " + who + "!")
})
}
// new async/await syntax - work with Promises in a most elegant fashion
// no more callback parameter; async implicitly returns a Promise
async function displayName (list) {
let texts = []
for (let item of list)
texts.push(await msgAfterTimeout(item.name, 200))
return texts
}
var list = [{name:"foo",surname:"bar"},{name:"Jean",surname:"dupond"}]
// instead of a callback, chain a .then on the returned Promise
displayName(list).then(texts => console.log("Done!", texts.join(' ')))
// Done! Hello foo! Hello Jean!
Note: If you need to support older browsers, async/await needs to be transpiled using something like Babel.
How can I perform dynamic chaining in Javascript Promises, all the time I have seen only hardcoding of the calls for eg., (promise).then(request/functionName).then(request/functionName)
Given an array functions that all return promises, you can use reduce() to run them sequentially:
var myAsyncFuncs = [
function (val) {return Promise.resolve(val + 1);},
function (val) {return Promise.resolve(val + 2);},
function (val) {return Promise.resolve(val + 3);},
];
myAsyncFuncs.reduce(function (prev, curr) {
return prev.then(curr);
}, Promise.resolve(1))
.then(function (result) {
console.log('RESULT is ' + result); // prints "RESULT is 7"
});
The example above uses ES6 Promises but all promise libraries have similar features.
Also, creating the array of promise returning functions is usually a good candidate for using map(). For example:
myNewOrmModels.map(function (model) {
return model.save.bind(model);
}).reduce(function (prev, curr) {
return prev.then(curr);
}, Promise.resolve())
.then(function (result) {
console.log('DONE saving');
});
One option is to utilize the properties of objects and the ability to invoke them via strings.
I wrote a small sample Here and posted it below.
The idea is that you have the set of functions that you wish to run set in some namespace or object, as I did in 'myNamespace':
myNamespace = {
"A": function() {return "A Function";},
"B": function() {return "B Function";},
"C": function() {return "C Function";}
}
Then your main promise would run and somehow (via inputs, ajax, prompts, etc.) you would get the string value of the function you want to have run, which isn't known until runtime:
My main promise uses a prompt to get a letter from the user:
var answer = prompt('Starting. Please pick a letter: A,B,C');
if(myNamespace[answer] === undefined)
{
alert("Invalid choice!");
reject("Invalid choice of: " + answer);
}
else
{
resolve(answer);
}
In the next 'then' I use that value (passed via the resolve function) to invoke the function:
.then(function(response) {
funcToRun = myNamespace[response]();})
Finally, I output to html the result of my dynamic function call and I use some recursive fun to make it more interactive and demonstrate that it is dynamic:
.then(function(){
document.getElementById('result').innerHTML = funcToRun;})
.then(function(){
if(prompt("Run Again? (YES/NO)")==="YES")
{
doWork();
}
});
myNamespace = {
"A": function() {return "A Function";},
"B": function() {return "B Function";},
"C": function() {return "C Function";}
}
function doWork()
{
var funcToRun;
new Promise(function(resolve,reject) {
var answer = prompt('Starting. Please pick a letter: A,B,C');
if(myNamespace[answer] === undefined)
{
alert("Invalid choice!");
reject("Invalid choice of: " + answer);
}
else
{
resolve(answer);
}
})
.then(function(response) {
funcToRun = myNamespace[response]();})
.then(function(){
document.getElementById('result').innerHTML = funcToRun;})
.then(function(){
if(prompt("Run Again? (YES/NO)")==="YES")
{
doWork();
}
});
}
doWork();
<div id="result"></div>
Since promises unwrap, just continue to add then statements and it will continue to be chained together
function asyncSeries(fns) {
return fns.reduce(function(p, fn) {
return p.then(fn);
}, Promise.resolve());
}
Recursively is a pretty cool way to do it as well :)
function countTo(n, sleepTime) {
return _count(1);
function _count(current) {
if (current > n) {
return Promise.resolve();
}
return new Promise(function(resolve, reject) {
console.info(current);
setTimeout(function() {
resolve(_count(current + 1));
}, sleepTime);
});
}
}
This is ES7 way.
Let's say you have multiple promises defined in an array.
var funcs = [
_ => new Promise(res => setTimeout(_ => res("1"), 1000)),
_ => new Promise(res => setTimeout(_ => res("2"), 1000))
}
And you want to call like this.
chainPromises(funcs).then(result => console.log(result));
You can use async and await for this purpose.
async function chainPromises(promises) {
for (let promise of promises) { // must be for (.. of ..)
await promise();
}
}
This will execute the given functions sequentially(one by one), not in parallel. The parameter promises is an array of functions, which return Promise.
Plunker: http://plnkr.co/edit/UP0rhD?p=preview
I think the simplest way is:
const executePromises = function(listOfProviders){
const p = Promise.resolve(null);
for(let i = 0; i < listOfProviders.length; i++){
p = p.then(v => listOfProviders[i]());
}
return p;
};
I believe the above is basically equivalent to:
const executePromises = async function(listOfProviders) {
for(let i = 0; i < listOfProviders.length; i++){
await listOfProviders[i]();
}
};
This solution based on usage promises of introduced in the EcmaScript 6 (https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise), so before use it see table browser`s support https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise#Browser_compatibility
Code
var f1 = function(){
for (var i = 0; i < 800000000; i++) {}
console.log('Function1 is done');
}
var f2 = function(){
for (var i = 0; i < 800000000; i++) {}
console.log('Function2 is done');
}
var f3 = function(){
for (var i = 0; i < 800000000; i++) {}
console.log('Function3 is done');
}
var f4 = function(){
for (var i = 0; i < 800000000; i++) {}
console.log('Function4 is done');
}
callbacks = function(){
// copy passed arguments
var callbacks = arguments;
// create array functions
var callbacks = Object.keys(callbacks).map(function(el){ return callbacks[el] });
var now = Date.now();
callbacks.reduce(function(previousPromise, currentFunc){
return previousPromise.then(
function(){
currentFunc();
var seconds = (Date.now() - now) / 1000;
console.log('Gone', seconds, 'seconds');
}
)
}, Promise.resolve());
}
callbacks(f1, f2, f3, f4);
Result in Chrome console (values seconds will be different):
Function1 is done
Gone 1.147 seconds
Function2 is done
Gone 2.249 seconds
Function3 is done
Gone 3.35 seconds
Function4 is done
Gone 4.47 seconds
Notes:
It is does not work if a function contains a timer (for this problem
I try also jQuery`s $Callbacks, $.Ajax and $.When but it not help.
The only decision, what I found, usage resolve() in callback of a
timer, but it is not acceptable if you have completed functions.).
Testing environment
$ google-chrome --version
Google Chrome 53.0.2785.116
I just had a problem with my api provider that doing Promise.all() would end up in concurrency db problems..
The deal with my situation is that i need to get every promise result in order to show some "all ok" or "some got error" alert.
And i don't know why .. this little piece of code who uses reduce when the promises resolved i couldn't get my scope to work (too late to investigate now)
$scope.processArray = function(array) {
var results = [];
return array.reduce(function(p, i) {
return p.then(function() {
return i.then(function(data) {
results.push(data);
return results;
})
});
}, Promise.resolve());
}
So thanks to this post http://hellote.com/dynamic-promise-chains/ I came with this little bastard.. It's not polished but it's working all right.
$scope.recurse = function(promises, promisesLength, results) {
if (promisesLength === 1) {
return promises[0].then(function(data){
results.push(data);
return results;
});
}
return promises[promisesLength-1].then(function(data) {
results.push(data);
return $scope.recurse(promises, promisesLength - 1, results);
});
}
Then i invoke the function like this:
var recurseFunction = $scope.recurse(promises, promises.length, results);
recurseFunction.then(function (response) { ... });
I hope it helps.
To build on this answer, if you want to return the result of all those promises, too:
async function chainPromises(promises) {
let results = [];
for (let promise of promises) {
let result = await promise();
results.push(result);
}
return results;
}
Check the following tutorial for
programmatic (dynamic) chaining of javascript/node.js promises and
Promise chaining using recursive functions
Programmatic-Chaining-and-Recursive-Functions-with-JavaScript-Promise
When using jQuery promises sequentially, it is possible to chain them using then repeatedly:
e.g.
promise = promise.then(someoperation());
which also works inside a loop (very handy).
I have similar scenario where I needed to know when multiple parallel operations were completed, but not go through the coding overhead (e.g. added complexity) of creating an array of promises for the sole purpose of calling $.when.apply
After looking at my options, I came up with this pattern as an alternative:
promise = $.when(promise, anotherpromise);
To test it I came up with this test:
var p = $.Deferred().resolve().promise();
[1,2,3,4,5,6,7,8,9,10].forEach(function(i){
p = $.when(p, delay(i,i * 500));
});
p.then(function(){
log("All done");
});
JSFiddle: http://jsfiddle.net/TrueBlueAussie/0rh8Lhv4/1/
Which appears to work just fine, so I started applying it to other example on StackOverflow.
The next one I tried with this pattern was to fix the example from Pass in an array of Deferreds to $.when():
My code:
$("a").click(function () {
var promise = GetSomeDeferredStuff();
promise.then(function () {
$("div").append("<p>All done!</p>");
});
});
JSFiddle: http://jsfiddle.net/TrueBlueAussie/ts1dqwe3/1/
Q. For some reason this one never fires the final event. Can anyone spot the problem?
Update
Based on a comment from #Karl-André Gagnon, it seems the initial promise can just be undefined and still work. Much simpler:
e.g.
var p;
[1,2,3,4,5,6,7,8,9,10].forEach(function(i){
p = $.when(p, delay(i,i * 500));
});
p.then(function(){
log("All done");
});
Okay, it turns out this pattern does indeed work just fine, but you need to ensure the initial promise you chain to is already resolved:
function GetSomeDeferredStuff() {
var promise = $.Deferred().resolve().promise();
JSFiddle: http://jsfiddle.ne/TrueBlueAussie/ts1dqwe3/2/
In summary this pattern really is a simple alternative to creating an array just for use by $.when.apply.
Also, as pointed out by #Karl-André Gagnon, if you start with an undefined value, it does the same thing. Even better :)
function GetSomeDeferredStuff() {
var promise;
JSFiddle: http://jsfiddle.net/TrueBlueAussie/ts1dqwe3/4/
Updated
I have similar scenario where I needed to know when multiple parallel
operations were completed, but not go through the overhead of creating
an array of promises for the sole purpose of calling
loop not utilized ; only $.when() ; same pattern as utilized when implementing through $.map() . Additionally , added "random" delay to jsfiddle ajax request ; promise.then() should not be called until all parallel function calls at $.when() completed , next called , queueName queue empty .
function GetSomeDeferredStuff(elem, name, cycles) {
var el = (elem || {}),
queueName = (name || "q"),
len = (cycles || 5),
request = function () {
return $.ajax({
type: "POST",
url: '/echo/html/',
data: {
html: "<p>Task #" + $.now() + " complete.",
delay: Math.random() * 5
},
success: function (data) {
$("div").append(data);
}
})
}
return $(el).queue(queueName, function (next) {
return $.when(request()
, request()
, request()
, request()
, request())
.then(next)
}).dequeue(queueName).promise(queueName);
}
$(function () {
$("a").click(function () {
var promise = GetSomeDeferredStuff();
promise.then(function () {
$("div").append("<p>All done!</p>");
});
});
});
jsfiddle http://jsfiddle.net/ts1dqwe3/10/
When using jQuery promises sequentially, it is possible to chain them
using then repeatedly
...
which also works inside a loop (very handy).
Try utilizing .queue() .promise(queueName)
function GetSomeDeferredStuff(elem, name, cycles) {
// if no `elem`:`this` object passed , utilize empty object `{}`
var el = (elem || {})
// if no `name` passsed, utilize `String` `"q"`
, queueName = (name || "q")
// if no `cycles` how many functions passed to `.queue(queueName)`,
// pass `5` functions to `.queue(queueName)`
, len = (cycles || 5);
return $(el).queue(queueName
, $.map(Array(len), function (_, index) {
return function (next) {
return $.ajax({
type: "POST",
url: '/echo/html/',
data: {
html: "<p>Task #" + (1 + index) + " complete.",
delay: (index + 1) / 2
},
success: function (data) {
return $("div").append(data);
}
// call "next" function in `queue`
}).then(next)
}
// `.dequeue(queueName)` , return `queueName` jQuery promise object,
// when all functions in `queue(queueName)` called ;
// `.queue(queueName)` empty
})).dequeue(queueName).promise(queueName);
}
$(function () {
$("a").click(function () {
var promise = GetSomeDeferredStuff();
promise.then(function () {
// `this`:`elem` , or `{}`
$("div").append("<p>All done!</p>");
});
});
});
jsfiddle http://jsfiddle.net/ts1dqwe3/6/
I have a standard javascript object whose prototype is extended with a .start() method taking 2 callbacks as arguments: success and failure respectively. This method performs some asynchronous processing (it's not AJAX) and depending on the result of this processing it invokes either the success or the failure callbacks.
Here's how this could be schematized:
function MyObject() {
}
MyObject.prototype.start = function(successCallback, errorCallback) {
(function(s, e) {
window.setTimeout(function() {
if (Math.random() < 0.8) {
s();
} else {
e();
}
}, 2000);
})(successCallback, errorCallback);
}
It's not really important the exact processing performed inside the method, only that it is asynchronous and non-blocking. I have no control over the point of time when the start method will finish the processing. I also have no control over the prototype and implementation of this method.
What I have control over is the success and failure callbacks. It is up to me to provide them.
Now I have an array of those objects:
var arr = [ new MyObject(), new MyObject(), new MyObject() ];
The order of elements in this array is important. I need to trigger the .start() method on each element of the array consecutively but only once the previous has completed (i.e. the success callback was called). And if an error occurs (the failure callback is called) I want to stop the execution and no longer invoke the .start method on the remaining elements of the array.
I could implement this naively by using a recursive function:
function doProcessing(array, index) {
array[index++].start(function() {
console.log('finished processing the ' + index + ' element');
if (index < array.length) {
doProcessing(array, index);
}
}, function() {
console.log('some error ocurred');
});
}
doProcessing(arr, 0);
This works fine but looking at the jQuery's deferred Object that was introduced in jQuery 1.5 I think that there is a room for improvement of this code. Unfortunately I don't feel very comfortable yet with it and I am trying to learn it.
So my question is is it possible to adapt my naive code and take advantage of this new API and if yes, could you provide me with some pointers?
Here's a jsfiddle with my implementation.
You could do something like this: (jsFiddle)
function MyObject() {
}
MyObject.prototype.start = function(queue) {
var deferred = $.Deferred();
//only execute this when everything else in the queue has finished and succeeded
$.when.apply(jQuery,queue).done(function() {
window.setTimeout(function() {
if (Math.random() < 0.8) {
deferred.resolve();
} else {
deferred.reject();
}
}, 2000);
});
return deferred;
}
var arr = [ new MyObject(), new MyObject(), new MyObject() ];
var queue = new Array();
$.each(arr, function(index, value) {
queue.push(value.start(queue)
.done(function() {
console.log('succeeded ' + index);
})
.fail(function() {
console.log('failed ' + index);
}));
});
Not quite sure wether you would consider this an improvement, though.
When we program, to remember the GRASP principles or guidelines is very important.
http://en.wikipedia.org/wiki/GRASP_(object-oriented_design)
To get High Cohesion and Low Coupling means that our code will be better, more reusable and easier to maintain.
So, the class MyObject mustn't known the queue existance. MyObject will know its own features and methods and anything more.
// Class MyObject
function MyObject(name) {
this.name = name;
}
MyObject.prototype.start = function() {
var deferred = $.Deferred();
var self = this;
setTimeout(function() {
if (Math.random() <= 0.8) {
console.log(self.name + "... ok");
deferred.resolve();
} else {
console.log(self.name + "... fail");
deferred.reject();
}
}, 1000);
return deferred.promise();
}
The main/caller function will know MyObject existance and it will create three instances that they will be executed sequentially.
// Create array of instances
var objectArray = [ new MyObject("A"), new MyObject("B"), new MyObject("C") ];
// Create array of functions to call start function
var functionArray = [];
$.each(objectArray, function(i, obj) {
functionArray.push(
function() {
return obj.start();
}
);
});
// Chain three start calls
$.iterativeWhen(functionArray[0], functionArray[1], functionArray[2])
.done(function() {
console.log("First: Global success");
// Chain three start calls using array
$.iterativeWhen.apply($, functionArray)
.done(function() {
console.log("Second: Global success");
})
.fail(function() {
console.log("Second: Global fail");
});
})
.fail(function() {
console.log("First: Global fail");
});
I have built a plugin for jQuery: iterativeWhen. It works with jQuery 1.8 and later versions.
$.iterativeWhen = function () {
var deferred = $.Deferred();
var promise = deferred.promise();
$.each(arguments, function(i, obj) {
promise = promise.then(function() {
return obj();
});
});
deferred.resolve();
return promise;
};
Jsfiddle here: http://jsfiddle.net/WMBfv/
There's nothing wrong with your implementation. And as we all know, using jQuery isn't always the best method.
I'd do it like this: (without the need to modify the MyObject class..)
function doProcessing(array, index) {
var defer = new $.Deferred();
$.when(defer).then(doProcessing);
array[index++].start(function() {
log('finished processing the ' + index + ' element');
if (index < array.length) {
defer.resolve(array, index);
}
}, function() {
log('some error ocurred => interrupting the process');
});
};
As you can see, there's no real advantage over the plain JavaScript method. :)
Here's my fiddle: http://jsfiddle.net/jwa91/EbWDQ/