From https://www.learnwithjason.dev/blog/keep-async-await-from-blocking-execution, I saw the 1st block of code log wrong values for post, while 2nd block of code logs correct values.
Utility Functions for both wrong and right versions
function getBlogPosts() {
const posts = [
{ id: 1, title: 'Post One', body: 'A blog post!' },
{ id: 2, title: 'Post Two', body: 'Another blog post!' },
{ id: 3, title: 'Post Three', body: 'A third blog post!' },
];
return new Promise((resolve) => {
setTimeout(() => resolve(posts), 200);
});
}
function getBlogComments(postId) {
const comments = [
{ postId: 1, comment: 'Great post!' },
{ postId: 2, comment: 'I like it.' },
{ postId: 1, comment: 'You make interesting points.' },
{ postId: 3, comment: 'Needs more corgis.' },
{ postId: 2, comment: 'Nice work!' },
];
// get comments for the given post
const postComments = comments.filter((comment) => comment.postId === postId);
return new Promise((resolve) => {
setTimeout(() => resolve(postComments), 300);
});
}
Wrong version The ...post always contains details from the 3rd post, even though comments are from post 1 and 2
function loadContent() {
getBlogPosts().then((posts) => {
for (post of posts) {
getBlogComments(post.id).then((comments) => {
console.log({ ...post, comments });
});
}
});
}
loadContent();
Correct version Comments from post 1 and 2 are correctly stored with respectively posts
async function loadContent() {
const posts = await getBlogPosts();
// instead of awaiting this call, create an array of Promises
const promises = posts.map((post) => {
return getBlogComments(post.id).then((comments) => {
return { ...post, comments };
});
});
// use await on Promise.all so the Promises execute in parallel
const postsWithComments = await Promise.all(promises);
console.log(postsWithComments);
}
loadContent();
1. The "wrong version" does not log the desired result because
the post variable is defined as a global variable rather a block-scoped variable
by the time then handlers get called the post variable is already set to the third post from the array. Here is a more detailed example explained: https://whistlr.info/2021/async-and-tasks/
You can verify it by logging post after all:
function loadContent() {
getBlogPosts().then((posts) => {
for (post of posts) {
getBlogComments(post.id).then((comments) => {
console.log({ ...post, comments });
});
}
});
}
loadContent();
console.log(post);
This should throw in strict mode.
2. The fix would be to use let/const in place of the post variable declaration e.g.:
function loadContent() {
getBlogPosts().then((posts) => {
// Use block-scoped let to avoid the variable
// leaking to the global scope
for (let post of posts) {
getBlogComments(post.id).then((comments) => {
console.log({ ...post, comments });
});
}
});
}
loadContent();
Note that even in strict mode using var modifier won't help as it is scoped to the enclosing function scope not a block scope compared to let/const.
More context here: https://javascript.info/var