Search code examples
javascriptweb-worker

Create array using foreach loop and pass it back with webworker postMessage


I have this web worker code:

onmessage = (e) => {
  console.log(e);
  fetch('https://example.com/wp-json/wp/v2/posts?per_page=20')
  .then( (res) => res.json() )
  .then( async (data) => {
    let imagesData = [];
    data.forEach( (item) => {
      fetch(item._links["wp:featuredmedia"][0].href)
      .then( (res) => res.json() )
      .then( (img) => {
        imagesData.push({
          title: item.title.rendered,
          link: item.link,
          src: img.source_url
        });
      });
    });
    await postMessage([imagesData]);
  });
} //end onmessage

I'm trying to create an array after that all the resources are fetched but I'm not able to achive this. at the moment the imagesData array will be empty and if moove it into the .then() where the array data are pushed, I will get multiple messages that will contain one single entry per array. How I can fix this to have a single array that will contain all the fetched information to pass back where the worker is created?

NB: The code works fine in my main script, I'm just mooving it to a web worker! The problem isn't with the data provided from fetch and his structure but with the imagesData array that is the one I want to populate and send back to the main script.


Solution

  • Array#forEach executes synchronously, so by the time you get to postMessage, the array will still be empty.

    Instead, what you need is Promise.all.

    Promise.all turns an array of promises into a single promise that resolves to an array of their resolved values. The inner promises are executed in parallel, so the time to resolution should be roughly only as long as that of the longest-running inner promise.

    Conveniently, passing an async function as the callback to Array#map transforms the original array into an array of promises. A common usage maps over an array of URL strings or parameters using fetch requests, wrapping the result in Promise.all:

    const usernames = await Promise.all(
        ['123', '456', '789'].map(async id => {
            const res = await fetch(`/api/users/${id}`)
            const { username } = await res.json()
    
            return username
        })
    )
    

    In your case, you need to replace this:

    let imagesData = [];
    data.forEach( (item) => {
      fetch(item._links["wp:featuredmedia"][0].href)
        .then( (res) => res.json() )
        .then( (img) => {
          imagesData.push({
            title: item.title.rendered,
            link: item.link,
            src: img.source_url
          });
        });
      });
    await postMessage([imagesData]);
    

    With this:

    const imagesData = await Promise.all(
        data.map(async item => {
            const res = await fetch(item._links["wp:featuredmedia"][0].href)
            const img = await res.json()
            return {
                title: item.title.rendered,
                link: item.link,
                src: img.source_url
            }
        })
    )
    

    In addition, a worker's postMessage is synchronous, so there's no need to await it. You also don't need to wrap imagesData in an outer array before posting it:

    postMessage(imagesData);