--- description: 'Enforce consistent awaiting of returned promises.' --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; > 🛑 This file is source code, not the primary documentation location! 🛑 > > See **https://typescript-eslint.io/rules/return-await** for documentation. This rule builds on top of the [`eslint/no-return-await`](https://eslint.org/docs/rules/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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await#improving_stack_trace). - When the `return` statement is in `try...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](https://github.com/tc39/proposal-faster-promise-adoption). ## Options ```ts 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: | Option | Ordinary Context
(stylistic preference 🎨) | Error-Handling Context
(catches bugs 🐛) | Should I use this option? | | :-------------------------------: | :----------------------------------------------: | :----------------------------------------------------------: | :--------------------------------------------------------: | | `always` | `return await promise;` | `return await promise;` | ✅ Yes! | | `in-try-catch` | `return promise;` | `return await promise;` | ✅ Yes! | | `error-handling-correctness-only` | don't care 🤷 | `return await promise;` | 🟡 Okay to use, but the above options would be preferable. | | `never` | `return 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`: ```ts option='"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 { 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'); } ``` ```ts option='"in-try-catch"' async 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 { 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'); } ``` ### `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`: ```ts option='"always"' async function invalidAlways1() { try { return Promise.resolve('try'); } catch (e) {} } async function invalidAlways2() { return Promise.resolve('try'); } async function invalidAlways3() { return await 'value'; } ``` ```ts option='"always"' async function validAlways1() { try { return await Promise.resolve('try'); } catch (e) {} } async function validAlways2() { return await Promise.resolve('try'); } async function validAlways3() { return 'value'; } ``` ### `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`: ```ts option='"error-handling-correctness-only"' async function asyncFunction(): Promise { if (Math.random() < 0.5) { return await Promise.resolve(); } else { return Promise.resolve(); } } ``` ### `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](https://github.com/typescript-eslint/typescript-eslint/issues/9433). ::: Examples of code with `never`: ```ts option='"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'; } ``` ```ts option='"never"' async function validNever1() { try { return Promise.resolve('try'); } catch (e) {} } async function validNever2() { return Promise.resolve('try'); } async function validNever3() { return 'value'; } ```