1

I'm practicing with some Promises and closures. I have a forEach loop where I return a Promise with a 3 second timeout and after the Promise resolves, it should log a statement.

I think I'm doing this incorrectly because I'm expecting every 3 seconds to see a log of "111" followed by "222" however I am seeing a delay of 3 seconds then immediately 3 logs of "111" "222".

let arr = [1,2,3];

arr.forEach((x,i) => {
  (function() {
       return new Promise((resolve,reject) => {
          setTimeout(() => {
             console.log("111")
             resolve(true)
          }, 3000);
       })

  })()
  .then(() => {console.log("222")})
});
2
  • 2
    Each iteration of your for loop occurs immediately one after another, queuing a new setTimeout() per each iteration. So, each setTimeout more or less get queued at the same time, and will complete ~3s after they were all queued Commented Dec 11, 2020 at 2:55
  • 1
    To get a better understanding of why this is the case, you should learn more about Javascript's Event Loop. Commented Dec 11, 2020 at 3:00

1 Answer 1

1

You simply forgot to tell javascript to "await" for that timeout between each iteration of the for loop. So what's happening it that javascript will run the for loop, schedule three timeouts while doing so, then those three timeouts all go off at once.

If you add an await like so, then it'll work as you expected.

(async function() {
  let arr = [1, 2, 3];

  for (let x of arr) {
    await (function() { // <-- await added
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          console.log("111")
          resolve(true)
        }, 3000);
      })

    })()
    .then(() => {
      console.log("222")
    })
  }
})()

I switched to for-of, because .forEach() doesn't work with async functions. I also wrapped the whole thing inside an async IIFE because a top-level await wasn't allowed there - depending on where you put this code, you might not have to wrap it inside an async IIFE.

EDIT

Just realized, you didn't use async/await stuff anywhere in your original question. I don't know if you've learned about it yet but you don't have to know it to solve this particular problem.

Here's another way to do it without async/await.

let arr = [1, 2, 3];

let promise = Promise.resolve();

arr.forEach((x,i) => {
  promise = promise
    .then(function() {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          console.log("111")
          resolve(true)
        }, 3000);
      })
    })
    .then(() => {
      console.log("222")
    })
});

This is basically building a promise chain inside the loop. If you "unwound" the loop, it would look like this:

promise = Promise.resolve()

promise = promise
  .then(() => /* wait 3000 ms and log "111" */)
  .then(() => { console.log("222") })
  .then(() => /* wait 3000 ms and log "111" */)
  .then(() => { console.log("222") })
  .then(() => /* wait 3000 ms and log "111" */)
  .then(() => { console.log("222") })

Because we're keeping around the a reference to the last promise, and we keep tacking onto the end of it, each new thing we tack on will happen after the last thing gets completed.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.