3

I have a simple useEffect in my component to fetch data from 3 API calls:

  useEffect(() => {
    Promise.all([
      fetch(`${baseUrl}/counts?entity=color`),
      fetch(`${baseUrl}/counts?entity=brand`),
      fetch(`${baseUrl}/counts?entity=category`)
    ]).then(([x, y, z])=> {
        console.log(x.json(), y.json(), z.json());
      });
  }, []);

I'm expecting the actual data I get from the API so that I can use it in my UI, but instead I'm getting this: enter image description here

I've also tried doing this:

  useEffect(() => {
    Promise.all([
      fetch(`${baseUrl}/counts?entity=color`),
      fetch(`${baseUrl}/counts?entity=brand`),
      fetch(`${baseUrl}/counts?entity=category`)
    ]).then(responses => responses.map(r => r.json()))
      .then(([x, y, z])=> {
        console.log(x, y, z);
      });
  }, []);

I see the desired value inside PromiseResult, but I'm not sure how to extract it: enter image description here

What am I doing wrong here. How do I access the array in my code?

Amol Borkar
  • 2,321
  • 7
  • 32
  • 63
  • Does this answer your question? [How do I return the response from an asynchronous call?](https://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call) – Robin Zigmond Apr 16 '23 at 13:58
  • 1
    I think you already understand at least a little about handling asynchronous code with Promises, but you seem to not be realising that the `json` method on a `fetch` result *itself* returns a Promise. In your case the simplest solution is probably to change `responses => responses.map(r => r.json())` to `responses => Promise.all(responses.map(r => r.json()))` – Robin Zigmond Apr 16 '23 at 14:01

4 Answers4

3

The issue is that .json returns another promise, so you'd need to call .then on it to get the eventual value. Or since you're doing multiple, you'd need to combine it with Promise.all, and then call .then on that.

One possibility is to tweak your .map attempt so that it has an additional Promise.all:

Promise.all([
  fetch(`${baseUrl}/counts?entity=color`),
  fetch(`${baseUrl}/counts?entity=brand`),
  fetch(`${baseUrl}/counts?entity=category`)
]).then(responses => {
  return Promise.all(responses.map(r => r.json()))
})
.then(([x, y, z])=> {
  console.log(x, y, z);
});

But that does have the downside that all of the fetches must get far enough along that they resolve, before any of them can call .json. This may slightly delay finishing things. So another possibility is to set up all of the work up front, and only call Promise.all with those final promises.

Promise.all([
  fetch(`${baseUrl}/counts?entity=color`).then(val => val.json()),
  fetch(`${baseUrl}/counts?entity=brand`).then(val => val.json()),
  fetch(`${baseUrl}/counts?entity=category`.then(val => val.json()))
]).then(([x, y, z])=> {
  console.log(x, y, z);
});

With that version, each of the fetches will go at whatever pace it can manage, and will convert itself to json as soon as it can. Then once all of them are finished, the combined promise resolves.

Nicholas Tower
  • 72,740
  • 7
  • 86
  • 98
1

You could combine your all fetch call and map() to generate an array of promises to easily loop the data:

const baseUrl = "https://jsonplaceholder.typicode.com";

  useEffect(() => {
    const urls = [`${baseUrl}/todos/1`, `${baseUrl}/todos/2`, `${baseUrl}/todos/3`];

    const fetchData = async () => {
      try {
        const responses = await Promise.all(urls.map(url => fetch(url)));
        const data = await Promise.all(responses.map(response => response.json()));
        console.log(data);
      } catch (error) {
        console.error(error);
      }
    };

    fetchData();
  }, []);
DSDmark
  • 1,045
  • 5
  • 11
  • 25
0

If you want to use await instead of then, this is how I would refactor your code:

useEffect(async () =>
  console.log(...await Promise.all(
    ['color','brand','category']
      .map(async i => (await fetch(`${baseUrl}/counts?entity=${i}`)).json())))
, []);
Andrew Parks
  • 6,358
  • 2
  • 12
  • 27
0

Write a getByEntity function that wraps a single fetch operation, then mao over the list of entities that you need to retrieve:

const baseUrl = '...';

// outside of your component
async function getByEntity(entity) {
  const response = await fetch(`${baseUrl}/counts?entity=${entity}`);
  const data = await response.json();
  return data;
}

// inside your component
useEffect(() => {
  Promise.all(
    ['color', 'brand', 'category'].map(getByEntity)
  ).then(([x, y, z])=> {
    console.log(x, y, z);
  });
}, []);
moonwave99
  • 21,957
  • 3
  • 43
  • 64