Returning Promises Could Crash Your App
Returning a Promise from an async function could crash your application. Learn about return await versus return and how to handle Promise related errors.
Table of Contents 📖
Returning Promises
All asynchronous functions return Promises. However, the error handling behavior varies depending on whether await is used or not with the return statement. In the code below, there are two asynchronous functions: returnErrorNoAwait and returnErrorAwait. The first function returns a rejected Promise without awaiting while the second function returns a rejected Promise while awaiting.
// Returns a rejected promise without waiting for it
async function returnErrorNoAwait() {
try {
return Promise.reject('returnErrorNoAwait');
} catch (err) {
console.error('Error has been caught!', err);
}
}
// Returns a rejected promise while waiting for it
async function returnErrorAwait() {
try {
return await Promise.reject('returnErrorAwait');
} catch (err) {
console.error('Error has been caught!', err);
}
}
// Run both functions and see what gets caught
async function main() {
try {
const errorAwait = await returnErrorAwait();
const errNoAwait = await returnErrorNoAwait();
} catch (err) {
console.error('Error NOT caught', err);
}
}
main();
Error has been caught! returnErrorAwait
Error NOT caught returnErrorNoAwait
By looking at the output, we can see that the returnErrorNoAwait function's rejected Promise is not caught in its try catch. Instead it relies on the outer try catch. Keep this in mind when writing asynchronous code. If you return the result of an asynchronous function without waiting for the Promise to settle, make sure you have an outer try catch to handle any potential errors.
Which to Use?
There is no right or wrong answer on handling Promise return. I have heard certain developers are afraid that return await will cause the application to resolve the Promise twice. However, we can disprove that pretty easily with the following code. Just toggle the presence of the await keyword in the delay function and see what happens.
// Returns a rejected promise without waiting for it
async function delay(ms) {
return await new Promise((resolve, reject) => {
setTimeout(resolve, ms);
});
}
// Run both functions and see what gets caught
async function main() {
console.log(new Date().getSeconds());
await delay(5000);
console.log(new Date().getSeconds());
}
main();
No matter the route you take, just be sure to handle errors accordingly. Also, the MDN web docs state the following:
INFO: Contrary to some popular belief, return await promise is at least as fast as return promise, due to how the spec and engines optimize the resolution of native promises. There's a proposal to make return promise faster and you can also read about V8's optimization on async functions. Therefore, except for stylistic reasons, return await is almost always preferable.