Linter · Rules
@typescript-eslint/no-misused-promises
Disallow promises where the code around them cannot handle one
A promise is an object, so it's always truthy, and if (isAdmin(user)) without await always takes the branch. An async function passed where a callback should return nothing is never awaited, so forEach moves on before the work is done, and nothing catches a rejection. A promise spread into an object adds no properties.
bun lint --type-aware turns this rule on in TypeScript files.
Configure
Off by default. This rule needs types, so turning it on makes bun lint type check your code first.
[lint.rules]
"@typescript-eslint/no-misused-promises" = "error" # or "warn"
Every option, set to its default:
[lint.rules]
"@typescript-eslint/no-misused-promises" = ["error", {
checksConditionals = true,
checksVoidReturn = {
arguments = true,
attributes = false,
inheritedMethods = true,
properties = true,
returns = true,
variables = true,
},
checksSpreads = true,
}]
Examples
Reported:
declare function isAdmin(id: string): Promise<boolean>;
declare function remove(id: string): Promise<void>;
declare function load(id: string): Promise<{ name: string }>;
function cleanUp(ids: string[]) {
if (isAdmin(ids[0])) {
ids.forEach(async id => {
await remove(id);
});
}
return { id: ids[0], ...load(ids[0]) };
}
Not reported:
declare function isAdmin(id: string): Promise<boolean>;
declare function remove(id: string): Promise<void>;
declare function load(id: string): Promise<{ name: string }>;
async function cleanUp(ids: string[]) {
if (await isAdmin(ids[0])) {
for (const id of ids) {
await remove(id);
}
}
return { id: ids[0], ...(await load(ids[0])) };
}
An async callback in filter keeps every item, because the promise it returns is truthy:
declare function isAdmin(id: string): Promise<boolean>;
const admins = ["ada", "lin"].filter(async id => await isAdmin(id));
Options
| Option | Default | What it does |
|---|---|---|
checksConditionals | true | Report a promise as a condition: in if, while, ? :, !, &&, ||, and returned from the callback of filter, find, some and the like. |
checksVoidReturn | { "attributes": false } | Report a function that returns a promise where a function that returns void is expected. |
checksSpreads | true | Report ... in front of a promise. |
checksVoidReturn is true, false, or an object that turns the check off in some places. A key you leave out is true.
JSX attributes aren't checked by default, because <form onSubmit={save}> with an async function is how most React code handles events. With an ESLint config file the default is typescript-eslint's, true.
| Key | Where the function is |
|---|---|
arguments | An argument of a call or of new. |
attributes | A JSX attribute. |
inheritedMethods | A method, when the class or interface it extends or implements declares it as returning void. |
properties | A property of an object. |
returns | A value that a function returns. |
variables | A value assigned to a variable. |
Not reported with checksVoidReturn: { arguments: false }:
declare function remove(id: string): Promise<void>;
function cleanUp(ids: string[]) {
ids.forEach(async id => {
await remove(id);
});
}
Not reported by default, and reported with checksVoidReturn: true:
declare namespace JSX {
interface IntrinsicElements {
form: { onSubmit?: () => void };
}
}
declare function save(): Promise<void>;
const form = <form onSubmit={save} />;
declare namespace JSX {
interface IntrinsicElements {
form: { onSubmit?: () => void };
}
}
declare function save(): Promise<void>;
const form = <form onSubmit={save} />;
checksConditionals also takes { "flagUnions": .. }, for a condition that's only sometimes a promise. With "none" (default) it's allowed. With "all" it's reported. With "strict" it's reported if the union has both the promise and what it resolves to, like number | Promise<number>.
Not reported by default:
declare function cached(key: string): number | Promise<number>;
if (cached("total")) {
console.log("found");
}
Reported with checksConditionals: { flagUnions: "strict" }:
declare function cached(key: string): number | Promise<number>;
if (cached("total")) {
console.log("found");
}