Reputation: 3896
I would like to execute functions one at a time and call another function when a function is finished. I was able to do it using callbacks but not using a promise chain. Here is what I tried (based on https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#chained_promises) but it executes the first 3 functions at the same time instead of waiting 1 second in each function:
function displayAll() {
var myPromise = (new Promise(display1))
.then((new Promise(display2))
.then((new Promise(display3))
.then(display4)));
}
function display1(resolve) {
setTimeout(function () {
console.log("display1");
resolve();
}, 1000);
}
function display2(resolve) {
setTimeout(function () {
console.log("display2");
resolve();
}, 1000);
}
function display3(resolve) {
setTimeout(function () {
console.log("display3");
resolve();
}, 1000);
}
function display4(resolve) {
setTimeout(function () {
console.log("display4");
}, 1000);
}
Do you know what is wrong with the code and if it is possible to do what I am trying to do without callbacks?
Upvotes: 1
Views: 866
Reputation: 19301
A walk through of displayAll
's order of execution:
var myPromise = (new Promise(display1))
The Promise
constructor calls display1
which sets up a timeout to log "display1" and resolve the promise. This works perfectly and the initial 1 second delay is honored.
.then(new Promise(display2))
then
method of myPromise
during execution of displayAll
.then
is evaluated before the making the call .then
's promise argument causes the Promise constructor to call display2
which sets up a timeout relative to displayAll
's time-of-execution.then
silently ignores the promise argument because it's not a function. Default handlers, used by then
in the absence of callable arguments, pass through incoming data or promise rejection reasons. .then(new Promise(display3))
operates the same as the previous then
clause: set up a timer relative to displayAll
's time-of-execution and use default handlers which pass through data or rejection reasons.
.then(display4)));
registers display4
as a handler to be called when the promise returned in step 3 becomes fullfilled. display4
sets up a workable timer to log "display4". Note display4
's argument is now misnamed - the argument passed to successive fulfillment handlers is the value returned by the previous promise handler in the chain.
The expected output of calling displayAll
is then to
displayAll
was called and log "display1".displayAll
was called, and log "display2".displayAll
was called, and log "display3".display4
as a handler, set up a timer to log "display4" - so it logs one second after "display3".One solution to stagger the execution of the display functions would be to move promise creation from where then
arguments are calculated into the handler itself. The handler can return the promise to delay proceeding down the promise chain until the returned promise is resolved by the timer:
function displayN() {
return new Promise( resolve =>
setTimer( function() {
console.log("displayN");
resolve();
}, 1000)
);
}
Other solutions and approaches are possible. Promisifying setTimeout
to create a promise that resolves after a specified interval can be useful for delaying steps in a promise chain or async
function. As a fairly minimal example:
function delayPromise( msec) {
return new Promise(resolve=> setTimeout( resolve, msec));
}
As an aside only, the value of a promise chain is the promise returned by the last then
, catch
or finally
call in the chain - promise chain values may be returned by a function but in practice at least are rarely recorded in a variable.
Upvotes: 1
Reputation: 1878
In order to chain Promise
s (MDN) you need to return a promise in then
method callback, instead you are constructing the promise as an argument of the then
method.
This will trigger the Promise as soon as is "encountered" the new
keyword, that is not the expected behaviour. You instead want to wait the first Promise
to end, and then chain the then
method that will create a new Promise
:
function displayAll() {
var myPromise = (new Promise(display1))
// .then(new Promise(display2)) <-- you are calling here the promise
.then(function() {
return new Promise(display2) // <-- here we return a promise to chain
})
.then(()=> new Promise(display3)) // same with arrow functions
.then(display4);
}
function displayAll() {
var myPromise = (new Promise(display1))
.then(()=> new Promise(display2))
.then(() => new Promise(display3))
.then(display4);
}
function display1(resolve) {
setTimeout(function () {
console.log("display1");
resolve();
}, 1000);
}
function display2(resolve) {
setTimeout(function () {
console.log("display2");
resolve();
}, 1000);
}
function display3(resolve) {
setTimeout(function () {
console.log("display3");
resolve();
}, 1000);
}
function display4(resolve) {
setTimeout(function () {
console.log("display4");
}, 1000);
}
displayAll()
You can also make your display functions return a Promise
such that you can pass them directly to the then
method:
function display1() {
return new Promise(resolve => {
setTimeout(function () {
console.log("display1");
resolve();
}, 1000);
});
}
function display2() {
return new Promise(resolve => {
setTimeout(function () {
console.log("display2");
resolve();
}, 1000);
});
}
function display3() {
return new Promise(resolve => {
setTimeout(function () {
console.log("display3");
resolve();
}, 1000);
});
}
function display4() {
return new Promise(resolve => {
setTimeout(function () {
console.log("display4");
resolve();
}, 1000);
});
}
let myPromise =
display1()
.then(display2)
.then(display3)
.then(display4)
Upvotes: 5