37

I would expect the code below to print one number on the console, then wait a second and then print another number. Instead, it prints all 10 numbers immediately and then waits ten seconds. What is the correct way to create a promise chain that behaves as described?

function getProm(v) {
    return new Promise(resolve => {
        console.log(v);
        resolve();
    })
}

function Wait() {
    return new Promise(r => setTimeout(r, 1000))
}

function createChain() {
    let a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];
    let chain = Promise.resolve();
    for (let i of a) {
        chain.then(()=>getProm(i))
            .then(Wait)

    }
    return chain;
}


createChain();
TLP
  • 1,262
  • 1
  • 8
  • 20

3 Answers3

40

You have to assign the return value of .then back to chain:

chain = chain.then(()=>getProm(i))
         .then(Wait)

Now you will basically be doing

chain
  .then(()=>getProm(1))
  .then(Wait)
  .then(()=>getProm(2))
  .then(Wait)
  .then(()=>getProm(3))
  .then(Wait)
  // ...

instead of

chain
  .then(()=>getProm(1))
  .then(Wait)

chain
  .then(()=>getProm(2))
  .then(Wait)

chain
  .then(()=>getProm(3))
  .then(Wait)
// ...

You can see that the first one is actually a chain, while the second one is parallel.

Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
1

See Resolve promises one after another (i.e. in sequence)?.

Using i of a:

function createChain() {
    let a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];
    let chain = Promise.resolve();
    for (let i of a) {
        chain = chain.then(()=>getProm(i))
            .then(Wait)
    }
    return chain;
}

Using a.forEach()

function createChain() {
    let a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];
    let chain = Promise.resolve();
    a.forEach(i =>
        chain = chain.then(()=>getProm(i))
            .then(Wait)
    );
    return chain;
}

Using a.reduce()

function createChain() {
    let a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];
    return a.reduce((chain, i) =>
        chain.then(()=>getProm(i))
            .then(Wait),
        Promise.resolve()
    );
}
Smiley1000
  • 95
  • 2
  • 10
-2

Now that we have await/async, a better way to do this is:

function getProm(v) {
    return new Promise(resolve => {
        console.log(v);
        resolve();
    })
}

function Wait() {
    return new Promise(r => setTimeout(r, 1000))
}

async function createChain() {
    let a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];
    for (let i of a) {
        await getProm(i);
        await Wait();
    }
}


createChain();
TLP
  • 1,262
  • 1
  • 8
  • 20
  • This is not a better way at all, because it is discouraged to use await inside a for loop. Internally the await syntax is just a helper for the engine which 'rewrites' the subsequent code (beyond the await keyword) as though it were wrapped in a "then". The program can't "continue" the for loop, after it has handed back the execution control to the event scheduler. In short: NEVER use await inside a for loop. – Gabriel Feb 04 '21 at 12:46