1

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. Why does the wrong version not log the correct post from the looping variable? Ideally the explanation can focus on how lexical environments work with Promises, assuming that's relevant to this bug
  2. Is there a way to fix the wrong version without map? Maybe for some reason I cannot have things run in parallel, which the map + Promise.all pattern does.
Han Qi
  • 137
  • 2
  • 7

1 Answers1

2

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

Eugene
  • 63
  • 5