JavaScript best practice: use return await

Author's image
Tamás Sallai
1 min
Photo by Aaron Burden on Unsplash

A common pattern is to call an async function and return its result:

const helloWorld = async () => {
	return await asyncHello("World");
}
// Hello World

Why the await? It would work exactly the same without it, so it's just taking up space.

Yes, but: let's add a try..catch block around it:

try {
	return await asyncHello("World");
}catch(e) {
	return "Whops";
}
// Whops

It works as expected: if the called function throws an error (rejects its Promise) then the catch block will run.

But now there is a difference:

try {
	return asyncHello("World");
}catch(e) {
	return "Whops";
}
// Uncaught Error

Why didn't the try..catch handle the Error? It was thrown from inside the block, yet it still wasn't caught.

await stops the function execution. So in the first case (return await) the helloWorld function is still running, the execution is inside the try..catch block.

But in the second case the function is already finished and did not stop in the try..catch. It returned the Promise the asyncHello returned without any additional error handling.

This happens not just with try..catch but also for cleanups that use the try..finally block:

export const withTempDir = async (fn) => {
	const dir = await createTempDir();
	try {
		return await fn(dir);
	}finally {
		await fs.rm(dir, {recursive: true});
	}
};

Without the await the temporary directory would be deleted when the fn runs. But with the await it waits for the fn to finish before cleaning up.

March 6, 2025

Free PDF guide

Sign up to our newsletter and download the "Foreign key constraints in DynamoDB" guide.