最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Promise.all in useEffect hook in React - Stack Overflow

programmeradmin1浏览0评论

I'm dealing with a use case where two API calls are made one after the other. The second API call is made by taking a value from the first API call response. I was able to use Promise.all with axios in my React application.

Is there a better way to call the APIs in chain in useEffect hook? I'm open to suggestions or remendations. Could anyone please help?

 useEffect(async () => {
    const getFirstResponse = async () => {
      try {
        return await axios.get('http://first-api', {
          params: { carId: id },
        });
      } catch (error) {
        return error;
      }
    };

    const firstResponse = await getFirstResponse();

    const getSecondResponse = async () => {
      try {
        return await axios.get('http://second-api', {
          params: { carName: firstResponse.data?.carName },
        });
      } catch (error) {
        return error;
      }
    };

    const secondResponse = await getSecondResponse();

    Promise.all([firstResponse, secondResponse])
      .then(function (values) {
        console.log(`values`, values);
      })
      .catch(function (err) {
        console.log(err);
      });

  }, []);

I'm dealing with a use case where two API calls are made one after the other. The second API call is made by taking a value from the first API call response. I was able to use Promise.all with axios in my React application.

Is there a better way to call the APIs in chain in useEffect hook? I'm open to suggestions or remendations. Could anyone please help?

 useEffect(async () => {
    const getFirstResponse = async () => {
      try {
        return await axios.get('http://first-api', {
          params: { carId: id },
        });
      } catch (error) {
        return error;
      }
    };

    const firstResponse = await getFirstResponse();

    const getSecondResponse = async () => {
      try {
        return await axios.get('http://second-api', {
          params: { carName: firstResponse.data?.carName },
        });
      } catch (error) {
        return error;
      }
    };

    const secondResponse = await getSecondResponse();

    Promise.all([firstResponse, secondResponse])
      .then(function (values) {
        console.log(`values`, values);
      })
      .catch(function (err) {
        console.log(err);
      });

  }, []);

Share Improve this question edited Jan 12, 2022 at 15:30 Jamiec 136k15 gold badges141 silver badges199 bronze badges asked Jan 12, 2022 at 15:26 krsnakrsna 4,3537 gold badges60 silver badges106 bronze badges 5
  • Please fix your script (and therefor fix the broken syntax highlighting) – Andreas Commented Jan 12, 2022 at 15:27
  • i do not understand why do you use promise.all at all in your case – Max Commented Jan 12, 2022 at 15:31
  • I want to initiate the second api call only when the first api call has a success response. Right now, I see that both API calls are made in parallel @Max – krsna Commented Jan 12, 2022 at 15:33
  • You're awaiting them, they aren't made in parallel. – Quentin Commented Jan 12, 2022 at 15:34
  • Don't ever catch (error) { return error; }! – Bergi Commented Jan 12, 2022 at 15:39
Add a ment  | 

3 Answers 3

Reset to default 4

Promise.all is pletely superfluous here.

It is a tool for handling promises that are running in parallel not in series.

The argument you pass to it should be an array of promises. firstResponse and secondResponse are the values that have been unwrapped from promises by await.

Just use firstResponse and secondResponse directly.

const secondResponse = await getSecondResponse();
console.log([firstResponse, secondResponse]);

For that matter, creating the nested async functions and having multiple try/catch blocks that do the same thing just makes the code harder to read.

You can reduce the whole thing down to:

useEffect(() => {
    const asyncFunction = async () => {
        try {
            const firstResponse = await axios.get('http://first-api', {
                params: { carId: id },
            });
            const secondResponse = await axios.get('http://second-api', {
                params: { carName: firstResponse.data?.carName },
            });
            console.log(`values`, [firstResponse, secondResponse]);
        } catch (error) {
            return error;
        }
      }
    asyncFunction();
}, []);

another possible way to get the result using Promise.all, but I like Quentin`s answer

try {
    const responses = await Promise.all([body1, body2].map(async body => {
        return await axios.get(body.endpoint, body.params);
    }))
} catch(error) {
    return error;
}

How about you write a custom hook that handles loading and error state so you don't have to rewrite it for each ponent?

  1. Avoid defining plex functions inside of useEffect
  2. Separate api calls into individual functions. This makes them reusable in other areas of your app
  3. Don't return errors on the resolved branch of your Promise. Instead let them reject and allow the caller to handle appropriately

Let's look at MyComponent. All plexity is removed and the ponent is only concerned with the three possible states of the asynchronous call -

// MyComponent.js

import { useAsync } from "./hooks"
import { fetchCarWithDetails } from "./api"

function MyComponent({ carId }) {
  const {loading, error, result} =
    useAsync(fetchCarWithDetails, [carId])  // reusable hook

  // loading...
  if (loading)
    return <p>Loading...</p>

  // error...
  if (error)
    return <p>Error: {error.message}</p>

  // result...
  return <pre>
    {JSON.stringify(result, null, 2)}
  </pre>
}

Our reusable api functions are defined in our api module -

// api.js

import axios from "axios"

function fetchCar(carId) {
  return axios
    .get('http://first-api', {params: {carId}})
    .then(r => r.data)
}

function fetchDetails(carName) {
  return axios
    .get('http://second-api', {params: {carName}})
    .then(r => r.data)
}

async function fetchCarWithDetails(carId) {
  const car = await fetchCar(carId)
  const details = await fetchDetails(car.carName)
  return { car, details }
}

export { fetchCar, fetchDetails, fetchCarWithDetails }

Reusable hooks are defined in our hooks module -

// hooks.js

function useAsync(f, [_0, _1, _2, _3, _4]) {
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState(null)
  const [result, setResult] = useState(null)
  useEffect(_ => {
    setLoading(true)
    Promise.resolve(f(_0, _1, _2, _3, _4))
      .then(setResult, setError)
      .finally(_ => setLoading(false))
  }, [f, _0, _1, _2, _3, _4])
  return {loading, error, result}
}

export { useAsync }
发布评论

评论列表(0)

  1. 暂无评论