@typescript-eslint/no-misused-promises
Disallow promises where the code around them cannot handle one
Also called typescript-eslint/no-misused-promises, typescript/no-misused-promises
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#
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])Expected non-Promise value in a boolean conditional.) {
ids.forEach(async id => {Promise returned in function argument where a void return was expected.
await remove(id);
});
}
return { id: ids[0], ...load(ids[0])Expected a non-Promise value to be spread in an object. };
}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)Expected a non-Promise value to be returned.);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 }:
{ "checksVoidReturn": { "arguments": false } }No problemsdeclare 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} />;{ "checksVoidReturn": true }1 problemdeclare namespace JSX {
interface IntrinsicElements {
form: { onSubmit?: () => void };
}
}
declare function save(): Promise<void>;
const form = <form onSubmit={save}Promise-returning function provided to attribute where a void return was expected. />;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" }:
{ "checksConditionals": { "flagUnions": "strict" } }1 problemdeclare function cached(key: string): number | Promise<number>;
if (cached("total")Expected non-Promise value in a boolean conditional.) {
console.log("found");
}