Skip to content
NewBun 1.4.5 is out. Release notes
AphrodyBun

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.

bunfig.toml
[lint.rules]
"@typescript-eslint/no-misused-promises" = "error" # or "warn"

Every option, set to its default:

bunfig.toml
[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

OptionDefaultWhat it does
checksConditionalstrueReport 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.
checksSpreadstrueReport ... 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.

KeyWhere the function is
argumentsAn argument of a call or of new.
attributesA JSX attribute.
inheritedMethodsA method, when the class or interface it extends or implements declares it as returning void.
propertiesA property of an object.
returnsA value that a function returns.
variablesA 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");
}