Skip to main content

return-await

Enforce consistent awaiting of returned promises.

🔧

Some problems reported by this rule are automatically fixable by the --fix ESLint command line option.

💡

Some problems reported by this rule are manually fixable by editor suggestions.

💭

This rule requires type information to run.

This rule builds on top of the eslint/no-return-await rule. It expands upon the base rule to add support for optionally requiring return await in certain cases.

The extended rule is named return-await instead of no-return-await because the extended rule can enforce the positive or the negative. Additionally, while the core rule is now deprecated, the extended rule is still useful in many contexts:

How to Use

eslint.config.mjs
export default tseslint.config({
rules: {
// Note: you must disable the base rule as it can report incorrect errors
"no-return-await": "off",
"@typescript-eslint/return-await": "error"
}
});

Try this rule in the playground ↗

Options

See eslint/no-return-await's options.

type Options =
| 'in-try-catch'
| 'always'
| 'error-handling-correctness-only'
| 'never';

const defaultOptions: Options = 'in-try-catch';

The options in this rule distinguish between "ordinary contexts" and "error-handling contexts". An error-handling context is anywhere where returning an unawaited promise would cause unexpected control flow regarding exceptions/rejections. See detailed examples in the sections for each option.

  • If you return a promise within a try block, it should be awaited in order to trigger subsequent catch or finally blocks as expected.
  • If you return a promise within a catch block, and there is a finally block, it should be awaited in order to trigger the finally block as expected.
  • If you return a promise between a using or await using declaration and the end of its scope, it should be awaited, since it behaves equivalently to code wrapped in a try block followed by a finally.

Ordinary contexts are anywhere else a promise may be returned. The choice of whether to await a returned promise in an ordinary context is mostly stylistic.

With these terms defined, the options may be summarized as follows:

OptionOrdinary Context
(stylistic preference 🎨)
Error-Handling Context
(catches bugs 🐛)
Should I use this option?
alwaysreturn await promise;return await promise;✅ Yes!
in-try-catchreturn promise;return await promise;✅ Yes!
error-handling-correctness-onlydon't care 🤷return await promise;🟡 Okay to use, but the above options would be preferable.
neverreturn promise;return promise;
(⚠️ This behavior may be harmful ⚠️)
❌ No. This option is deprecated.

in-try-catch

In error-handling contexts, the rule enforces that returned promises must be awaited. In ordinary contexts, the rule enforces that returned promises must not be awaited.

This is a good option if you prefer the shorter return promise form for stylistic reasons, wherever it's safe to use.

Examples of code with in-try-catch:

async function invalidInTryCatch1() {
try {
return Promise.reject('try');
} catch (e) {
// Doesn't execute due to missing await.
}
}

async function invalidInTryCatch2() {
try {
throw new Error('error');
} catch (e) {
// Unnecessary await; rejections here don't impact control flow.
return await Promise.reject('catch');
}
}

// Prints 'starting async work', 'cleanup', 'async work done'.
async function invalidInTryCatch3() {
async function doAsyncWork(): Promise<void> {
console.log('starting async work');
await new Promise(resolve => setTimeout(resolve, 1000));
console.log('async work done');
}

try {
throw new Error('error');
} catch (e) {
// Missing await.
return doAsyncWork();
} finally {
console.log('cleanup');
}
}

async function invalidInTryCatch4() {
try {
throw new Error('error');
} catch (e) {
throw new Error('error2');
} finally {
// Unnecessary await; rejections here don't impact control flow.
return await Promise.reject('finally');
}
}

async function invalidInTryCatch5() {
return await Promise.resolve('try');
}

async function invalidInTryCatch6() {
return await 'value';
}

async function invalidInTryCatch7() {
using x = createDisposable();
return Promise.reject('using in scope');
}
Open in Playground

always

Requires that all returned promises be awaited.

This is a good option if you like the consistency of simply always awaiting promises, or prefer not having to consider the distinction between error-handling contexts and ordinary contexts.

Examples of code with always:

async function invalidAlways1() {
try {
return Promise.resolve('try');
} catch (e) {}
}

async function invalidAlways2() {
return Promise.resolve('try');
}

async function invalidAlways3() {
return await 'value';
}
Open in Playground

error-handling-correctness-only

In error-handling contexts, the rule enforces that returned promises must be awaited. In ordinary contexts, the rule does not enforce any particular behavior around whether returned promises are awaited.

This is a good option if you only want to benefit from rule's ability to catch control flow bugs in error-handling contexts, but don't want to enforce a particular style otherwise.

info

We recommend you configure either in-try-catch or always instead of this option. While the choice of whether to await promises outside of error-handling contexts is mostly stylistic, it's generally best to be consistent.

Examples of additional correct code with error-handling-correctness-only:

async function asyncFunction(): Promise<void> {
if (Math.random() < 0.5) {
return await Promise.resolve();
} else {
return Promise.resolve();
}
}
Open in Playground

never

Disallows awaiting any returned promises.

warning

This option is deprecated and will be removed in a future major version of typescript-eslint.

The never option introduces undesirable behavior in error-handling contexts. If you prefer to minimize returning awaited promises, consider instead using in-try-catch instead, which also generally bans returning awaited promises, but only where it is safe not to await a promise.

See more details at typescript-eslint#9433.

Examples of code with never:

async function invalidNever1() {
try {
return await Promise.resolve('try');
} catch (e) {}
}

async function invalidNever2() {
return await Promise.resolve('try');
}

async function invalidNever3() {
return await 'value';
}
Open in Playground

When Not To Use It

Type checked lint rules are more powerful than traditional lint rules, but also require configuring type checked linting.

See Troubleshooting > Linting with Type Information > Performance if you experience performance degradations after enabling type checked rules.

Resources

Taken with ❤️ from ESLint core.