mbj
mbj

Reputation: 107

Async/await inside for...of vs. map

I'm trying to figure out why promises seem to work differently inside of for...of vs. map().

data is an array of objects of the form {app: MY_APP, link: MY_LINK}. I'm trying to convert it to the form {app: MY_APP, status: true OR false}}. The function checkLink() is async because it uses node-fetch and basically returns whether the given link is 200. Using for...of, I get the desired object:

let objToSend = [];
for (obj of data) {
  objToSend.push({ app: obj.app, status: await checkLink(obj.link) });
}

// returns [{app: MY_APP, status: true}, ...]

But using map, as follows, I get an array of pending promises instead. Any help would be appreciated:

let objToSend = data.map(async obj => {
  return {
    app: obj.app,
    status: await checkLink(obj.link)
  };
});

// returns [ Promise { <pending> }, ...]

I also tried doing Promise.all(objToSend) after the map code, but it instead returns Promise { <pending> }

Upvotes: 3

Views: 3579

Answers (3)

3limin4t0r
3limin4t0r

Reputation: 21130

I'll leave the explanation up to the other answers, but just want to point out that there is also a performance difference.

Your first solution waits on every iteration for the promise to resolve, only calling checkLink if the previous one has resolved. This is a sequential solution.

check link1 => wait => check link2 => wait => check link3 => wait

The second solution iterates over every element and sends out requests, without waiting for promises to resolve; For this reason, an array of promises is returned. If you wait for all promises to be resolved you find this solution is a lot faster, since the requests are sent out in parallel.

check link1 => check link2 => check link3 => wait for all

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

async function checkLink(link) {
  await sleep(Math.random() * 1000 + 500); // sleep between 500 and 1500 ms
  console.log(link);
  return `status #${link}`;
}

(async function () {
  const data = new Array(5).fill().map((_, index) => ({ app: "app", link: index }));
  let objToSend;
  
  console.log("solution #1");
  objToSend = [];
  for (let obj of data) {
    objToSend.push({ app: obj.app, status: await checkLink(obj.link) });
  }
  console.log(objToSend);

  console.log("==============================================");
  
  console.log("solution #2");
  objToSend = await Promise.all(data.map(async obj => {
    return {
      app: obj.app,
      status: await checkLink(obj.link)
    };
  }));
  console.log(objToSend);
})();

In the snippet, the first solution takes 500/1500 * 5 = 2500/7500 between 2500 and 7500 ms, while the second solution takes between 500 and 1500 ms (depending on the slowest value to resolve).

Upvotes: 4

FZs
FZs

Reputation: 18619

Async functions always returns Promises. In your map function, as the callback returns promises, an array of promises is being created.

To convert it to your format, use it with Promise.all():

(async()=>{
    let objToSend = await Promise.all(data.map(async obj => {
        return {
            app: obj.app,
            status: await checkLink(obj.link)
        };
    }));
    console.log(objToSend) //[{app: MY_APP, status: true}, ...]
})()

Upvotes: 2

Jonas Wilms
Jonas Wilms

Reputation: 138307

The await always halts the execution of the async function it is in, it doesn't work differently in both cases. In the later example however, you do call some async functions, and those calls will evaluate to promises, whereas in your first example, all those awaits are in the same async function.

I also tried doing Promise.all(objToSend) after the map code, but it instead returns Promise { <pending> }

Yup, await that and you get an array of results.

Upvotes: 1

Related Questions