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.
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);