Think you know async functions? Check out this resolve behavior.
Async functions are used to wait for data, but what happens if you return a value that didn’t require any waiting at all?
If you want to early-exit a function by just returning a static value which was immediately ready, will that value need to be wrapped in a Promise? Let’s see!
Code Example
import { fetchFromRemote, requiredParam } from 'package'
const myFunc = async (): Promise<string> => {
if (!requiredParam) {
const localString = "local string";
return localString;
}
const fetchedString = await fetchString(requiredParam);
return fetchedString;
}
Breaking Expectations
The function returns a Promise that resolves to EITHER a local string
or a fetched string
, but the type definition said only Promise would be acceptable. Considering this, we might have expected a type error here:
if (!requiredParam) {
const localString = "local string";
return localString; // Expected error: Type 'string' is not assignable to type 'Promise<string>'.
}
Shouldn’t we have been forced to wrap the returned local string
in a Promise?
if (!requiredParam) {
const localString = "local string";
const localPromise = Promise.resolve(localString); // Attempt to avoid error: wrap the local string in a Promise.
return localPromise;
}
The Answer
No! JavaScript automatically wraps the return values of async functions in Promises, even if it wasn’t specified within the function itself. So, in the synchronous branch where return "local string"
is encountered, JavaScript internally converts it to Promise.resolve("local string")
. This behavior ensures that regardless of the code path, the async function always fulfills its type definition of returning a Promise.
This automatic wrapping of values in Promises not only aligns with TypeScript’s type system but also simplifies the development process. Developers can write asynchronous functions more intuitively without explicitly wrapping each return value in a Promise, leading to cleaner and more concise code.