Async operations
This page discusses best practices for writing promises and async/await and managing async operations.
Promises
no-async-promise-executor
- Severity: error
From the ESLint docs:
The executor function can also be an
async function
. However, this is usually a mistake, for a few reasons:
- If an async executor function throws an error, the error will be lost and won't cause the newly-constructed
Promise
to reject. This could make it difficult to debug and handle some errors.- If a Promise executor function is using
await
, this is usually a sign that it is not actually necessary to use thenew Promise
constructor, or the scope of thenew Promise
constructor can be reduced.
no-promise-executor-return
- Severity: error
- Configuration:
- Do not allow returning a
void
expression (allowVoid: false
)
- Do not allow returning a
The executor's return value is ignored and you may be confusing it with the resolve
function. Do not use concise arrow functions with promise executors, because it usually does not make the code much shorter.
prefer-promise-reject-errors
- Severity: error
- Configuration:
- Do not allow rejecting with nothing (
allowEmptyReject: false
)
- Do not allow rejecting with nothing (
Just like you should always throw
an Error
instance, you should always reject
with an Error
instance. This means when people use try...catch
with await
, they can catch the error and handle it properly.
Async functions
require-await
- Severity: warning
You should not define an async
function that does not await
anything. This means the users have to unnecessarily await
to get the return value.
This is only a warning, because there are many cases where the lack of await
is intentional:
- The function is a stub and will be implemented later.
- It's a callback function and the caller will
await
it. You want to remind readers thatawait
is allowed in this function. - The caller explicitly requires a
Promise
instance to be returned. - The function calls another function that is planned to become async in the future.
- The function is a method that derives from an async base method.
You can use your discretion to disable this rule in these cases.
Generator functions
require-yield
- Severity: warn
For the same reasons as require-await
, this is only set to a warning because there are cases where a generator function is semantically more appropriate.
Paralleling
no-await-in-loop
- Severity: off
While this rule enforces a practice that we are in favor of—prefer paralleling promises instead of firing sequentially, the number of false-positives is overwhelming. Sometimes async operations' order does matter, for example, if we care about the priority of each operation, or when each operation modifies the global state.
ts
importfs from "fs-extra";/*** When multiple files match, we return the one with priority of JSON > YAML > TOML*/async functionfindDataFile () {constfileNames = ["data.json", "data.yml", "data.toml"];for (constfileName offileNames ) {if (awaitfs .pathExists (fileName )) {returnfileName ;}}return null;}
ts
importfs from "fs-extra";/*** When multiple files match, we return the one with priority of JSON > YAML > TOML*/async functionfindDataFile () {constfileNames = ["data.json", "data.yml", "data.toml"];for (constfileName offileNames ) {if (awaitfs .pathExists (fileName )) {returnfileName ;}}return null;}
The corresponding "nice" way to do this is too obscure and far above being understandable by average-intelligents, and extracting it as a utility function is almost never worthwhile.
ts
functionexecuteAsyncSequential <T >(arr :T [],action : (a :T ) =>Promise <void>) {returnarr .reduce ((res ,elem ) =>res .then (() =>action (elem )),Promise .resolve (),);}functionfindAsyncSequential <T >(arr :T [],matcher : (a :T ) =>Promise <boolean>) {constnotFound =Symbol ("not found");returnarr .reduce <Promise <T | typeofnotFound >>(async (res ,elem ) => {if ((awaitres ) !==notFound ) returnres ;if (awaitmatcher (elem )) return awaitelem ;returnnotFound ;},Promise .resolve (notFound )).then ((res ) => (res ===notFound ?undefined :res ));}
ts
functionexecuteAsyncSequential <T >(arr :T [],action : (a :T ) =>Promise <void>) {returnarr .reduce ((res ,elem ) =>res .then (() =>action (elem )),Promise .resolve (),);}functionfindAsyncSequential <T >(arr :T [],matcher : (a :T ) =>Promise <boolean>) {constnotFound =Symbol ("not found");returnarr .reduce <Promise <T | typeofnotFound >>(async (res ,elem ) => {if ((awaitres ) !==notFound ) returnres ;if (awaitmatcher (elem )) return awaitelem ;returnnotFound ;},Promise .resolve (notFound )).then ((res ) => (res ===notFound ?undefined :res ));}
Racing
require-atomic-updates
- Severity: warning
You should generally avoid side effects. When side effects are unavoidable, make sure that only one function is able to update the variable. This rule is helpful, but there can be false-positives, because it can only enforce defensive coding but not locate any actual offenders.