Isaac
Isaac

Reputation: 12874

forEach is not working but map is

An extension of this question, the solution by @CertainPerformance is working fine.

const query = qs.stringify({ ...API_QUERY_PARAMS, q: this.state.searchString });
const url = `https://www.googleapis.com/youtube/v3/search?${query}`

const { data } = await axios.get(url);

await Promise.all(data.items.map(async (vid) => {
  let id = vid.id.videoId; //Individual video ID
  const individualQuery = qs.stringify({ ...INDIVIDUAL_API_QUERY_PARAMS, id      });
  const individualURL = `https://www.googleapis.com/youtube/v3/videos?${individualQuery}`;
  const { data } = await axios.get(individualURL);
  vid.statistics = data.items[0].statistics
}))

this.setState({ videos: data.items });

I was thinking it could be use forEach instead of map. However if I swap to forEach it will do nothing and state.videos will return nothing.

Checking from this article, specifically this statement

Well, the forEach() method doesn’t actually return anything (undefined). It simply calls a provided function on each element in your array. This callback is allowed to mutate the calling array.

And hence forEach theoretically should work too but why it's not the case? For example, the forEach should behave like below which able to mutate the calling array

let items = [
  {id: '123', title: 'John'},
  {id: '123', title:'sammy'}
]

items.forEach(x=> {
	x['statistics'] = { propA: 'A', propB: 'B'};
})

console.log(items);

Upvotes: 2

Views: 439

Answers (1)

CertainPerformance
CertainPerformance

Reputation: 370679

You need map because Promise.all requires an argument of an array, generally with all or some of those elements in the array as Promises. As you see, forEach does not return an array. In fact, it won't run at all once it reaches that point, an error will be thrown:

Promise.all(undefined).then(() => console.log('ok'))

(index):30 Uncaught (in promise) TypeError: Cannot read property 'Symbol(Symbol.iterator)' of undefined at Function.all () at window.onload ((index):30)

You might be missing that async functions automatically return Promises that resolve once the end of their (visual) block is reached. Translating the async function into a standard function does exactly the same thing and explicitly returns a Promise, the code would look like this:

await Promise.all(data.items.map((vid) => {
  const id = vid.id.videoId;
  const individualQuery = qs.stringify({ ...INDIVIDUAL_API_QUERY_PARAMS, id      });
  const individualURL = `https://www.googleapis.com/youtube/v3/videos?${individualQuery}`;
  return axios.get(individualURL)
    .then(({ data }) => {
      vid.statistics = data.items[0].statistics
    });
}))

You have to return the created Promise so that Promise.all knows that it need to wait for all Promises in the array to resolve first - this was somewhat masked behind the async function of the original code.

Upvotes: 2

Related Questions