Search code examples
javascriptpromisees6-promise

How to chain promises together in JavaScript


I'm trying to chain a sequence of Promises so that the second promise will start after the first one resolves and so on. I don't understand how I cannot get it to work correctly.

Here is my demo code:

    const promise1 = new Promise((resolve, reject) => { setTimeout(resolve, 5000, 'a'); });
    const promise2 = new Promise((resolve, reject) => { setTimeout(resolve, 5000, 'b'); });
    const promise3 = new Promise((resolve, reject) => { setTimeout(resolve, 5000, 'c'); });

    promise1.then(val => {
      console.log('promise 1', val);
      promise2.then(val2 => {
        console.log('promise 2', val2);
        promise3.then(val3 => {
          console.log('promise 3', val3);
        });
      });
    });

So what I expected from this code goes like this:

--- 5 seconds passes ---
// console outputs: 'promise 1a'
--- 5 seconds passes ---
// console outputs: 'promise 2b'
--- 5 seconds passes ---
// console outputs: 'promise 3c'

But instead what happens:

--- 5 seconds passes ---
// console outputs: 'promise 1a'
// console outputs: 'promise 2b'
// console outputs: 'promise 3c'

Why is this so? Why are all three promises triggered at the same time?


Solution

  • For the purposes of explaining why they all finish at the same time, we can ignore everything except the promise declarations:

    const promise1 = new Promise((resolve, reject) => { setTimeout(resolve, 5000, 'a'); });
    const promise2 = new Promise((resolve, reject) => { setTimeout(resolve, 5000, 'b'); });
    const promise3 = new Promise((resolve, reject) => { setTimeout(resolve, 5000, 'c'); });
    

    These promises are created HOT (i.e. the handler passed to the constructor is immediately invoked), and were all created at the same time. It follows that the setTimeout within will fire in 5 seconds time, no matter how they are subsequently used.

    How they are used subsequently is incidental to this, however, to get your example working, it might be better to write functions that return Promise when called... so:

    const getPromise1 = () => new Promise((resolve, reject) => {
      setTimeout(resolve, 5000, 'a');
    });
    const getPromise2 = () => new Promise((resolve, reject) => {
      setTimeout(resolve, 5000, 'b');
    });
    const getPromise3 = () => new Promise((resolve, reject) => {
      setTimeout(resolve, 5000, 'c');
    });
    
    getPromise1().then((val1) => {
      console.log(val1);
      return getPromise2(); //by returning Promise, you can avoid deep nesting
    }).then((val2) => {
      console.log(val2);
      return getPromise3();
    }).then((val3) => {
      console.log(val3);
    });

    In answer to comment, it might be better to have an array of arguments that are used to feed to a Promise-returning function, then to use async/await to write a function that calls the Promise-returning function in sequence

    const getPromise = (v) => new Promise((resolve, reject) => {
      setTimeout(resolve, 5000, v);
    });
    
    async function run(params) {
      for (const p of params) {
        const returnVal = await getPromise(p);
        console.log(returnVal);
      }
    }
    
    run(['a', 'b', 'c']);