-2

Please note - this is NOT a generic Javascript question about async/await vs. .then. If you are curious about that, please look here: Difference of using async / await vs promises?.


This is a question about surfacing commonly known edge cases when using either of these two methods within the context of: 1/ useEffect, 2/ using a setState in that useEffect, and 3/specifically when doing a fetch in the useEffect.

Are either of these two methods are better able to handle common edge cases?


I would prefer to 1/ future-proof the code by using the method that better handles these edge cases and 2/ learn about the common edge cases to know what situations to consider.

Thank you!

// Method 1 useEffect(() => { const response = fetch('/api/tacos', { method: 'GET', headers: { 'Content-Type': 'application/json', }, }) .then((response) => response.json()) .then(({ data }) => { console.log('Success:', data); setTacosArray(() => data); }) .catch((error) => { console.error('Error:', error); }); }, []); //Method 2 useEffect(() => { const fetchData = async () => { try { const response = await fetch('/api/tacos', { method: 'GET', headers: { 'Content-Type': 'application/json', }, }); const { data } = await response.json(); console.log('Success:', data); setTacosArray(() => data); } catch (error) { console.error('error', error); } }; fetchData(); }, []); 
1

1 Answer 1

-1

In your code, one very common scenario you want to account for is the need to check for response.ok (and potentially other response codes if you'd like to get more granular) and then handle it appropriately if you do not get the response.ok.

I handled it below in the code snippets and as you can see, the chained .then statements keep the error statements within the particular "steps" of the chain. In the async/await, the checks/response handling are within the main try block. As you can see from the code, the handling of these edge cases are so similar that at the end of the day this becomes a matter of preference.

// Method 1 useEffect(() => { const response = fetch('/api/tacos', { method: 'GET', headers: { 'Content-Type': 'application/json', }, }) .then((response) =>{ // ** CHECK AND HANDLE VARIOUS RESPONSES if (response.ok) { return response.json(); } else { throw new Error('Response NOT okay');} }) .then(({ data }) => { console.log('Success:', data); setTacosArray(() => data); }) .catch((error) => { console.error('Error:', error); }); }, []); //Method 2 useEffect(() => { const fetchData = async () => { try { const response = await fetch('/api/tacos', { method: 'GET', headers: { 'Content-Type': 'application/json', }, }); // ** CHECK AND HANDLE VARIOUS RESPONSES if (response.ok) { const { data } = await response.json(); console.log('Success:', data); setTacosArray(() => data); } else { console.log('Response NOT okay') } } catch (error) { console.error('error', error); } }; 

However, these types of issues may not be the big picture of what side effects or issues may come up with data-fetching.

Most web frameworks are not very opinionated in terms of fetching/updating data and React is one of these frameworks. NextJS is more opinionated and has become more opinionated over the years ... it has the library swr, had created various methods including getStaticProps, getServerSideProps, Incremental Static Regeneration each with it's own caching and retrieval mechanism, and in NextJSv13.4+ there was an overhaul of the routing system, a move to use core WebAPI res/req objects instead of res/req node objects, and has introduced various default caching mechanisms across the board.

The meta point here is this ... you asked about chaining .this statements vs. async/await to ferret out what are the issues that can arise down the road as the app becomes more complex and how can you "future proof" the code. Well, the short answer is that the either way you do it, whatever issues that come up can be similarly handled with the .then or the async/await.

Now, from you question, I think you were trying to get a glimpse at what the bigger issues at play are here. And these issues go beyond the useEffect/fetch/.then vs. async/await but it is related to the synchronization of data between client and server which is essentially what your code handles. (The following is paraphrased from the TanStack Query AKA React Query docs.). Client state and server state are different. Most statement management libraries handle local state well but do not do as well with async/server state. This is due to the following reasons. Server state:

  • Is persisted remotely in a location you do not control or own
  • Requires asynchronous APIs for fetching and updating
  • Implies shared ownership and can be changed by other people without your knowledge
  • Can potentially become "out of date" in your applications if you're not careful

And then the synchronization between client state and server state brings up these issues:

  • Caching... (possibly the hardest thing to do in programming)
  • Deduping multiple requests for the same data into a single request
  • Updating "out of date" data in the background
  • Knowing when data is "out of date"
  • Reflecting updates to data as quickly as possible
  • Performance optimizations like pagination and lazy loading data
  • Managing memory and garbage collection of server state
  • Memoizing query results with structural sharing

Long story short, either method you asked about in your question (.then vs async/await) is fine for what you are doing as specified by the code in your question.

When you want to do more optimizations, handle things like pagination, and have more control of automatic updates ... then you can turn to a library like TanStack Query.


Read more on TanStack Query AKA React Query here.

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.