Reputation: 107
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
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
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
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 function
s, and those calls will evaluate to promises, whereas in your first example, all those await
s are in the same async function
.
I also tried doing
Promise.all(objToSend)
after the map code, but it instead returnsPromise { <pending> }
Yup, await
that and you get an array of results.
Upvotes: 1