-2

It is often said that async / await and then() should not be mixed.

However, consider a scenario where we want a utility function that fetches user data, which we want to await upon consumption elsewhere in the code base:

const getUser = async () => {
  const userPromise = fetch(...)
    .then((res) => res.json())
    .then((user) => JSON.stringify(user));

  return userPromise;
};

Unless I have misunderstood, the above allows us to convert the return value of the fetch promise into a promise that returns the final user data in json format, when await'ed later:

const user = await getUser();

My idea was that nesting then() here ensures we do not have to write lines to unpack the json, when consuming getUser later.

Is that a valid use case for mixing then and async/await, or is it an antipattern?

Magnus
  • 6,791
  • 8
  • 53
  • 84
  • 4
    The only problem with *mixing* `async`/`await` and promise API is when they are literally intermingled in the same place. Not if they are used *in the same project*. Moreover, the "problem" of mixing them isn't technical. It just has a chance of introducing confusion with the two different semantics. Although both are really the same thing at the end of the day. Such confusion can lead to things like [Is it an anti-pattern to use async/await inside of a new Promise() constructor?](https://stackoverflow.com/q/43036229) but the mix is not, by itself, bad. – VLAZ Feb 21 '23 at 20:11
  • 4
    In short - it's valid to mix promise API and `async`/`await` as long as you do it validly. Not when it introduces weird and potentially problematic interactions. If you *don't* know when that's the case, then stick to just one per function. But each function can have a different approach. – VLAZ Feb 21 '23 at 20:12
  • @VLAZ Thank you. Is my example code correct, or have I misunderstood how then works? As you probably understood, I do not want to await inside the utility function, as I use the utility function in parallel data fetching etc. – Magnus Feb 21 '23 at 21:10
  • It's syntactically valid. It also does I assume what you want. But the `.then((res) => res.json()).then((user) => JSON.stringify(user));` doesn't make much sense. Parse from JSON to a JS object then convert the JS object back to JSON. It's easier to just use `.then(res => res.text())` if you want the JSON. Although the code also doesn't handle failures with `fetch()` or even receiving a 4xx or 5xx error response, either. So, is it correct? ¯\\_(ツ)_/¯ Probably not. But you also probably use it for illustrative purposes. In which case "correct" hardly applies anyway. – VLAZ Feb 21 '23 at 21:14
  • @VLAZ Let me check .text(). I needed the user in a React component, thus the stringify. – Magnus Feb 21 '23 at 21:23
  • 1
    In a comment to a deleted answer you seem to think that `await` blocks, but it doesn't. You can also use `await` here. A function that doesn't use `await` shouldn't need to be `async`. – trincot Feb 21 '23 at 22:04
  • @trincot Thanks for the comment. Await does, by definition, await the return of the fetch. I do not want to await that right away. You can start the data fetching higher up in the component tree, together with a bunch of other asynchronous fetches, then await it further down. With React Server Components and Streaming / Suspense you then avoid blocking the UI. See here, for instance: https://beta.nextjs.org/docs/data-fetching/fetching#parallel-data-fetching – Magnus Feb 22 '23 at 05:11
  • @trincot PS: I am sure you know, but fetch is a Web API that runs outside of the JS Engine, on separate threads. It is not synchronous. It is often smart to start data fetching early on, when you believe it is likely that you will need it. – Magnus Feb 22 '23 at 05:14
  • "*Await does, by definition, await the return of the fetch*" `await` is a syntactic sugar over the promise API. It doesn't matter whether you do `x = await p; console.log(x)` or `p.then(x => console.log(x)`. It's going to be the same operation anyway. A function with awaits in it is no faster or slower, nor does it really work any differently, to a function with just `.then()` calls. – VLAZ Feb 22 '23 at 06:16
  • @VLAZ Thank you, then I think I have misunderstood `then()`. Do both `await` and `then()` halt the JS execution until the fetch is done (i.e. until the Promise has been resolved)? – Magnus Feb 22 '23 at 06:28
  • 1
    Neither halts the execution. `await p` will sleep the function and yield to the execution so other things can run. When `p` settles the function is resumed and queued back to continue running. In essence transforming `x = await p; console.log(x)` to `p.then(x => console.log(x)` – VLAZ Feb 22 '23 at 06:32
  • @VLAZ Hmmm, not sure I agree (I might be wrong though). See this: `function timeout(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }; const foo = await timeout(3000); console.log(foo);` The last statement will not run until the timeout runs and the promise has been resolved. – Magnus Feb 22 '23 at 07:03
  • @Bergi example here: https://stackoverflow.com/questions/33289726/combination-of-async-function-await-settimeout – Magnus Feb 22 '23 at 07:05
  • 1
    @Magnus will not run but doesn't block. The execuction isn't halted. – VLAZ Feb 22 '23 at 07:05
  • @VLAZ Ok, I think I get your point. Let me rephrase. Nothing below the await in the current execution context will run until the promise returns. Is that correct? – Magnus Feb 22 '23 at 07:06
  • The function won't continue. But that's the same if you had a `.then()` - the callback only fires after the promise is resolved. With `await` the later code also works once the promise resolves. – VLAZ Feb 22 '23 at 07:07
  • https://jsbin.com/xijezobete/1/edit?js,console – VLAZ Feb 22 '23 at 07:09
  • In the example code you shared it wouldn't change the behaviour of the returned promise if you would have used `await`. I think there is a misunderstanding you have about `await`. Sure there are differences with `then`, but the example code you have given is not demonstrating such difference. Using `await` here would not prevent parallel processing of other fetches. Your comments on now deleted answers show a misunderstanding of `await`. There is no blocking of UI. – trincot Feb 22 '23 at 07:48
  • Thank you both, I will go back to the drawing board and study it further. – Magnus Feb 22 '23 at 08:11

1 Answers1

1

async..await is purely syntactic sugar. Every .then can be represented in async..await and vice versa. Mixing the two is generally a code smell however, which can signal the writer doesn't fully understand either. For example, your function uses the async keyword but there's no await expression, therefore making it completely unnecessary.

const getUser = async () => { // ❌ no need for async
  const userPromise = fetch(...)
    .then((res) => res.json())
    .then((user) => JSON.stringify(user)); // ❌ re-encode JSON, why?

  return userPromise;
};

If you want to return the user JSON, consider response.text -

const getUser = () =>
  fetch(...).then(res => res.text()) // ✅

Further, comments indicate that you think async..await prevents "parallel" asynchronous operations. That's simply not the case. Every then expression has a perfectly equivalent async..await representation -

const getUser = async () => {
  const res = await fetch(...)
  return res.text() // ✅ await is not needed here!
}

Note return await .. is an anti-pattern as async functions implicitly return Promises!

Mulan
  • 129,518
  • 31
  • 228
  • 259