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
Promiseto 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 Promiseconstructor, or the scope of thenew Promiseconstructor can be reduced.
no-promise-executor-return
- Severity: error
- Configuration:
- Do not allow returning a
voidexpression (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
awaitit. You want to remind readers thatawaitis allowed in this function. - The caller explicitly requires a
Promiseinstance 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.
tsimportfs 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;}
tsimportfs 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.
tsfunctionexecuteAsyncSequential <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 ));}
tsfunctionexecuteAsyncSequential <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.