---
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 <br/> (stylistic preference 🎨) |        Error-Handling Context <br/> (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;` <br/> (⚠️ 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`:

<Tabs>
<TabItem value="❌ Incorrect">

```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<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');
}
```

</TabItem>
<TabItem value="✅ Correct">

```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<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');
}
```

</TabItem>
</Tabs>

### `always`

{/* insert option description */}

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`:

<Tabs>
<TabItem value="❌ Incorrect">

```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';
}
```

</TabItem>
<TabItem value="✅ Correct">

```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';
}
```

</TabItem>
</Tabs>

### `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`:

<Tabs>
<TabItem value="✅ Correct">

```ts option='"error-handling-correctness-only"'
async function asyncFunction(): Promise<void> {
  if (Math.random() < 0.5) {
    return await Promise.resolve();
  } else {
    return Promise.resolve();
  }
}
```

</TabItem>
</Tabs>

### `never`

{/* insert option description */}

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`:

<Tabs>
<TabItem value="❌ Incorrect">

```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';
}
```

</TabItem>
<TabItem value="✅ Correct">

```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';
}
```

</TabItem>
</Tabs>
