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:
- Returning an awaited promise improves stack trace information.
- When the
return
statement is intry...catch
, awaiting the promise also allows the promise's rejection to be caught instead of leaving the error to the caller. - Contrary to popular belief,
return await promise;
is at least as fast as directly returning the promise.
How to Use
module.exports = {
"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
options.
type Options = 'in-try-catch' | 'always' | 'never';
const defaultOptions: Options = 'in-try-catch';
in-try-catch
In cases where returning an unawaited promise would cause unexpected error-handling control flow, the rule enforces that await
must be used.
Otherwise, the rule enforces that await
must not be used.
Listing the error-handling cases exhaustively:
- if you
return
a promise within atry
, then it must beawait
ed, since it will always be followed by acatch
orfinally
. - if you
return
a promise within acatch
, and there is nofinally
, then it must not beawait
ed. - if you
return
a promise within acatch
, and there is afinally
, then it must beawait
ed. - if you
return
a promise within afinally
, then it must not beawait
ed. - if you
return
a promise between ausing
orawait using
declaration and the end of its scope, it must beawait
ed, since it behaves equivalently to code wrapped in atry
block.
- ❌ Incorrect
- ✅ Correct
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 Playgroundasync function validInTryCatch1() {
try {
return await Promise.reject('try');
} catch (e) {
// Executes as expected.
}
}
async function validInTryCatch2() {
try {
throw new Error('error');
} catch (e) {
return Promise.reject('catch');
}
}
// Prints 'starting async work', 'async work done', 'cleanup'.
async function validInTryCatch3() {
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) {
return await doAsyncWork();
} finally {
console.log('cleanup');
}
}
async function validInTryCatch4() {
try {
throw new Error('error');
} catch (e) {
throw new Error('error2');
} finally {
return Promise.reject('finally');
}
}
async function validInTryCatch5() {
return Promise.resolve('try');
}
async function validInTryCatch6() {
return 'value';
}
async function validInTryCatch7() {
using x = createDisposable();
return await Promise.reject('using in scope');
}
Open in Playgroundalways
Requires that all returned promises are await
ed.
Examples of code with always
:
- ❌ Incorrect
- ✅ Correct
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 Playgroundasync function validAlways1() {
try {
return await Promise.resolve('try');
} catch (e) {}
}
async function validAlways2() {
return await Promise.resolve('try');
}
async function validAlways3() {
return 'value';
}
Open in Playgroundnever
Disallows all await
ing any returned promises.
Examples of code with never
:
- ❌ Incorrect
- ✅ Correct
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 Playgroundasync function validNever1() {
try {
return Promise.resolve('try');
} catch (e) {}
}
async function validNever2() {
return Promise.resolve('try');
}
async function validNever3() {
return 'value';
}
Open in PlaygroundWhen Not To Use It
Type checked lint rules are more powerful than traditional lint rules, but also require configuring type checked linting. See Performance Troubleshooting if you experience performance degredations after enabling type checked rules.