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> }
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> }
- 3 Your callback returns a promise, so unsurprisingly it maps to promises. – tkausl Commented Jul 11, 2019 at 14:55
-
2
One way to think about this is that the
for
loop is a loop inside a single function. That single function returns a single promise. Amap()
is many function inside a loop. Each function returns a promise so you end up with an array of promises. – Mark Commented Jul 11, 2019 at 14:56 - This tweet might help you. twitter./ryanflorence/status/1264284487799595008?s=20 – Sagar Commented May 24, 2020 at 2:03
3 Answers
Reset to default 4I'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).
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}, ...]
})()
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.