1

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?

baptx
  • 3,428
  • 6
  • 33
  • 42
  • 3
    Passing `resolve` as a parameter doesn't make it a promise. You'll need to wrap the setTimeouts in a promise. – Phix Feb 12 '21 at 00:38
  • That's not true, the [`Promise` (MDN)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise) constructor takes a `function(resolve, reject)` as parameter, so the `display1` function is a valid argument – DDomen Feb 12 '21 at 00:52
  • 2
    The main problem here is that `.then()` requires that you pass it a function reference and you're passing it a promise. That's NOT how you use `.then()` and thus the promise you pass it is ignored. IMO, this should have been a TypeError when they designed promises and it's clearly a programming error, but `.then()` just ignores it. Instead, you need to pass a function that RETURNS your promise. This is critically important so that `.then()` can call your function LATER when the parent promise resolves. – jfriend00 Feb 12 '21 at 00:54
  • 2
    It's unfortunate the MDN has led you to this place. The code on that page is very misleading if you don't stare at this stuff all day long. It would be nearly impossible to find production, non-contrived, application code written in this manner. That's is the message from the other commenters. Also, while the answer works, it falls into the same category of contrived/won't find in the wild example. – Randy Casburn Feb 12 '21 at 01:00
  • I agree with you, there is no real well written and defined tutorial about promises, and also there is an historical reason of that: initially there weren't well defined standards for cross-browser async programming, so everyone implemented their own kind. But actually everything in JS is event / promise / callback driven so shall we say that all the JS is contrived? There are many libraries and frameworks that try to abstract the vanilla JS for this reason – DDomen Feb 12 '21 at 01:15
  • 2
    I just filed this issue with MDN to see if we can get that cleaned up: https://github.com/mdn/content/issues/2303 – Randy Casburn Feb 12 '21 at 01:16
  • Thanks for your comments and answers, I also noticed that I missed one parenthesis on the first 2 `.then()` and added them on the third `.then()` instead. – baptx Feb 12 '21 at 11:04

2 Answers2

5

In order to chain Promises (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);
}

From your code:

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()

Another more clear approach:

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)
DDomen
  • 1,808
  • 1
  • 7
  • 17
  • Nice explanation. – Md Kawser Habib Feb 12 '21 at 03:51
  • Thanks, there are also unnecessary parentheses in `var myPromise = (new Promise(display1))` which can be written `var myPromise = new Promise(display1)`. Your second approach is more clear indeed and allows to call a function like `display1()` directly without getting the error that resolve is undefined. Is there any advantage to use arrow function expressions? If we use it for the promise, I guess it would be better to use it for the setTimeout also to be consistent? – baptx Feb 12 '21 at 11:02
  • 1
    Yes the parentheses are unnecessary but at least is more clear which is the execution order. For arrow functions, in these simple examples there is pratically no difference, it is just a style choiche, but they are not strictly equal. I encourage you to deep the the arrow function arguments in this other thread: [Are 'Arrow Functions' and 'Functions' equivalent / interchangeable?](https://stackoverflow.com/questions/34361379/are-arrow-functions-and-functions-equivalent-interchangeable). – DDomen Feb 12 '21 at 12:57
1

A walk through of displayAll's order of execution:

  1. 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.

  1.  .then(new Promise(display2))
    
  • calls the then method of myPromise during execution of displayAll.
  • The argument for then is evaluated before the making the call .
  • Creating then's promise argument causes the Promise constructor to call display2 which sets up a timeout relative to displayAll's time-of-execution.
  • When called 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.
  1.  .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.

  2.  .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

  1. Delay a second after displayAll was called and log "display1".
  2. Delay a second, again after displayAll was called, and log "display2".
  3. Delay a second, again after displayAll was called, and log "display3".
  4. When promise chain handling executes 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.

traktor
  • 17,588
  • 4
  • 32
  • 53