Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork2.8k
docs: a long awaited blog post#11118
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Draft
kirkwaiblinger wants to merge5 commits intotypescript-eslint:mainChoose a base branch fromkirkwaiblinger:a-long-awaited-blog-post
base:main
Could not load branches
Branch not found:{{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline, and old review comments may become outdated.
Uh oh!
There was an error while loading.Please reload this page.
Draft
Changes fromall commits
Commits
Show all changes
5 commits Select commitHold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Jump to file
Failed to load files.
Loading
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
224 changes: 224 additions & 0 deletionspackages/website/blog/2025-04-29-promises.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,224 @@ | ||
--- | ||
authors: kirkwaiblinger | ||
description: How typescript-eslint's suite of Promise rules enforce best practices around async code | ||
slug: promises | ||
tags: [] | ||
title: 'Await For It: Writing Reliable Async Code' | ||
--- | ||
When working with asynchronous code, we have a few fundamental principles. | ||
- Async code should only be used when needed | ||
- Unhandled promise rejections are Bad. | ||
- Control flow should be explicit. | ||
Let's see our promise rules in action on some sample code that might come up during the development cycle, and see how they enforce our core principles. | ||
## Worked Example | ||
Imagine someone adds the following JS file in a code review. | ||
```js | ||
function doSomething() { | ||
fetch('https://example.com', { method: 'POST' }); | ||
} | ||
function doSomethingElse() { | ||
fetch('https://example.com', { method: 'PUT' }); | ||
} | ||
function getSomeValue() { | ||
return fetch('https://example.com', { method: 'GET' }); | ||
} | ||
export function foo() { | ||
doSomething(); | ||
doSomethingElse(); | ||
return getSomeValue(); | ||
} | ||
``` | ||
This code seems suspicious! | ||
Did the author really intend to kick off two write requests and a read request, then just return the result of a read request? | ||
Much more plausible is that they meant to kick off the write requests in parallel, and, after they're done, read the result to ensure it's correct. | ||
How can we catch these errors that depend on the implementations of other functions? | ||
The answer, [of course](./2024-09-30-typed-linting.md), is TypeScript types! | ||
Let's take a look at what the linter tells us: | ||
```ts | ||
function doSomething() { | ||
// [@typescript-eslint/no-floating-promises]: Promises must be awaited, end | ||
// with a call to .catch, end with a call to .then with a rejection handler | ||
// or be explicitly marked as ignored with the `void` operator. | ||
fetch('https://example.com', { method: 'POST' }); | ||
} | ||
``` | ||
Fair enough! | ||
We've just kicked off a task but not given anyone a way to know when it's finished. | ||
We'd best return the `fetch` promise. | ||
```ts | ||
// [@typescript-eslint/promise-function-async]: Functions that return | ||
// promises must be async. | ||
function doSomething() { | ||
// Remove this line | ||
fetch('https://example.com', { method: 'POST' }); | ||
// Add this line | ||
return fetch('https://example.com', { method: 'POST' }); | ||
} | ||
``` | ||
But not we have another complaint! Let's make that function `async`. | ||
Not only is it now unambiguously clear in code review that this is async code, the `async` keyword unlocks the `await` keyword. | ||
And, come to think of it, we'd better use that in order to make sure our request succeeded. | ||
```ts | ||
async function doSomething() { | ||
// Remove this line | ||
return fetch('https://example.com', { method: 'POST' }); | ||
// Added lines start | ||
const res = await fetch('https://example.com', { method: 'POST' }); | ||
return res.ok; | ||
// Added lines end | ||
} | ||
``` | ||
Following the linter complaints, we might end up in a state that looks like this: | ||
```ts | ||
async function doSomething() { | ||
const res = await fetch('https://example.com', { method: 'POST' }); | ||
return res.ok; | ||
} | ||
async function doSomethingElse() { | ||
const res = await fetch('https://example.com', { method: 'PUT' }); | ||
return res.ok; | ||
} | ||
async function getSomeValue() { | ||
const res = await fetch('https://example.com', { method: 'GET' }); | ||
return res.text(); | ||
} | ||
export async function foo() { | ||
// @typescript-eslint/no-floating-promises reports this line | ||
doSomething(); | ||
// @typescript-eslint/no-floating-promises reports this line | ||
doSomethingElse(); | ||
return getSomeValue(); | ||
} | ||
``` | ||
And now we're at a point where we can actually fix the suspicious control flow: | ||
```ts | ||
export async function foo() { | ||
// run these tasks in parallel | ||
const results = await Promise.all([doSomething(), doSomethingElse()]); | ||
// make sure they succeeded! | ||
if (!results.every(res => res)) { | ||
throw new Error('write requests failed!'); | ||
} | ||
// validate the outcome | ||
return getSomeValue(); | ||
} | ||
``` | ||
This type of deep analysis is just a small subset of what our Promise rules can offer, as a result of their usage of TypeScript types. | ||
## Promise rules | ||
The following table gives an overview of our core promise rules. See the individual rule docs for much more detail on each rule: | ||
| Rule | Description | | ||
| ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | | ||
| [await-thenable](/rules/await-thenable) | Ensure you don't `await` values that cannot be a promise | | ||
| [no-floating-promises](/rules/no-floating-promises) | Ensure that you don't forget to `await` statements that may need it. | | ||
| [no-misused-promises](/rules/no-misused-promises) | Ensures you don't provide promises to locations that don't expect promises. | | ||
| [prefer-promise-reject-errors](/rules/prefer-promise-reject-errors) | Ensure that you only reject promises with `Error` objects. | | ||
| [promise-function-async](/rules/promise-function-async) | Ensure that you use `async` syntax. | | ||
| [return-await](/rules/return-await) | Prevent subtle control flow errors in `async` functions and enforce consistent stylistic choices. | | ||
As seen in the example provided above, these rules work best in concert, rather than individually. | ||
We recommend you enable all of them (or simply [use our strict-type-checked preset configuration](/users/configs#strict-type-checked)). Taken together, these rules help ensure predictable control flow in asynchronous code and prevent unhandled promise rejections from occurring. | ||
## When you know better | ||
While our Promise rules lay out a framework of generally applicable best practices, there are situations where valid code doesn't fit into this framework. | ||
We don't recommend disabling the promise analysis rules entirely to handle these situations. | ||
Instead, we recommend two main strategies for dealing with these situations. | ||
### Ignoring code ad-hoc | ||
The first strategy is to use a [standard eslint-disable comment](https://eslint.org/docs/latest/use/configure/rules#using-configuration-comments-1). | ||
```ts | ||
async function foo() { | ||
// ... | ||
/* eslint-disable-next-line @typescript-eslint/no-floating-promises -- | ||
* This promise is ok to float because it won't reject and we don't | ||
* want to await it here for control flow reasons. | ||
*/ | ||
pinkiePromise(); | ||
// ... | ||
} | ||
``` | ||
Don't be too shy to use disable comments! | ||
We find that many users are averse to disabling rules, but it's an absolutely valid tool when the situation calls for it. | ||
:::tip | ||
Be sure to [include a meaningful description](https://eslint.org/docs/latest/use/configure/rules#comment-descriptions) so that you and your teammates remember why the rule is disabled! | ||
::: | ||
### Configuring rules to ignore certain patterns | ||
If you're using certain libraries, you may want to ignore certain patterns. | ||
For example, the `test()` function from [`node:test`](https://nodejs.org/api/test.html) may return a promise, but it should not always be awaited | ||
```ts | ||
import { describe, test } from 'node:test'; | ||
import * as assert from 'node:assert'; | ||
// no-floating-promises issues a report that can be ignored in this case. | ||
test('asynchronous top level test', async () => { | ||
assert.ok(true); | ||
}); | ||
``` | ||
:::warning | ||
This example is for illustrative purposes, not an explainer on `node:test`. | ||
If you are using `node:test`, please note that [sometimes, awaiting `test()` _is_ required](https://nodejs.org/api/test.html#subtests). | ||
::: | ||
We can configure the `no-floating-promises` rule to avoid this with a configuration like | ||
```json | ||
{ | ||
"@typescript-eslint/no-floating-promises": [ | ||
"error", | ||
{ | ||
"allowForKnownSafeCalls": [ | ||
{ | ||
"from": "package", | ||
"name": "test", | ||
"package": "node:test" | ||
} | ||
] | ||
} | ||
] | ||
} | ||
``` | ||
For further reading on this type of configuration, see the [`no-floating-promises` options](/rules/no-floating-promises#options) and the docs on our [`TypeOrValueSpecifier` format](/packages/type-utils/type-or-value-specifier). |
6 changes: 6 additions & 0 deletionspackages/website/blog/authors.yml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.