0

I'm experiencing some weird behavior when using promises inside of a map.

This isn't really a problem per se, but I'd like to understand what's going on.

let books = [
  {Name: "Moby Dick",
  AuthorId: 1
  },
  {Name: "The Great Gatsby",
  AuthorId: 2}
]

let authors = [
  {AuthorId: 1,
  Name: "Herman Melville"
  },
  {AuthorId: 2,
  Name: "F. Scott Fitzgerald"
  }
]

const getAuthorName = (AuthorId) => {
  return new Promise((resolve, reject) => {
     setTimeout(() => {
     resolve(authors.find((author) => {
        return author.AuthorId === AuthorId
        }).Name)
     }, 1000

  })
}
let responseArray = []
let promises = books.map((book) => (
  getAuthorName(book.AuthorId).then((res) => {
    responseArray.push({
      ...book,
      AuthorName: res
    })
  })
))

setTimeout(() => console.log(responseArray), 500)

//I would expect to have to do this:
//Promise.all(promises).then((res) => console.log(res))

I would expect the

setTimeout(() => console.log(responseArray), 5000) 

to log an empty string, because the array of promises hasn't been run through Promise.all yet, but it seems like even though the map should just be returning an array of promises, it's actually running the promises. Why is this?

Edit

I've edited the getAuthor promise to wait a second before resolving to further elaborate because that wasn't the point I was trying to get at.

I would expect mapping over an array, to return a new array with whatever is returned in the map. For example, if I do

let arrayOfPromises = books.map((book) => {
   return getAuthor(book.AuthorId)
}

I would expect arrayOfPromises to be an array of getAuthor promises.

However, when I throw a .then() on the end of the promise that is being returned, it appears that the code in the .then() is evaluating.

So if I do

let promises = books.map((book) => {
   return getAuthor(book.AuthorId).then((res) => {
      console.log(res)
   })
}

In the console I'll see "Herman Merville" and "F. Scott Fitzgerald" and in the promises var I'll have an array of promises.

While I thought that each getAuthor's .then would only evaluate once I call Promise.all(promises) because the getAutor promise being returned inside the map. Am I understanding something wrong here?

1
  • Promises are just that, a promise of something yet to come. It might have already occurred, however. For instance, I could give you Promise.resolve(5) and that's a promise for the value 5. It has already "run". Commented Aug 8, 2019 at 19:59

1 Answer 1

2

That's because the code inside your promise isn't asynchronous, so it's resolving instantly as there's nothing to wait for.

To expand a bit on this, what you are doing is the same as

const getAuthorName = (AuthorId) => {
  return Promise.resolve(authors.find((author) => {
    return author.AuthorId === AuthorId
  }).Name)
}

It will allow you to chain a then syntax on the function, but that also mean that the then will be executed directly after the function call.

edit

Following your edit, here's a bit more.

You've modified the method to be asynchronous using the setTimeout. Which means that the map is now return an array of promises as you expected.

However there's another issue with your code, you have also encapsulated the console.log in a setTimeout, one with an even greater timer!

To better understand what is going on here, I highly recommend that you watch this video.

However in the spirit of StackOverflow, I'll add some written explanations here.

You are first creating an array of promises, executing callbacks after approximately 1s after their creations (they won't wait for the map to be over).

As soon as they are done, they go in what's called the event loop of your browser. Basically it's a pile of events that your browser will handle as soon as the main thread is freed.

So 1s after their creations the promises will resolve, and place their callbacks on this event loop.

Now what happens when you run your console.log.

Well if you didn't put it in its own setTimeout, it would print an empty array.

What if it was a setTimeout of 500ms? Here we can safely assume that the map would take less than 500ms to finish and start the new setTimeout so the console.log would reach the event loop first and print an empty array once again.

What about 5s? Both setTimeouts will be over long before the console.log is reached, so you will print an filled array.

I made a jsfiddle if you want to try it out for yourself.

I hope this helps clear things up.

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

2 Comments

I've edited my original question, does that help elaborate the issue?
@AndreFuller I made an edit to answer your question better.

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.