2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ESLint の no-floating-promises で await し忘れを検出する

2
Posted at

はじめに

TypeScript + React でコードを書いていると、async 関数を呼び出すときに await を書き忘れることがあります。

async function onClickRefresh() {
  setIsLoading(true);
  const records = fetchAllRecords(); // ← await を書き忘れ!
  setRecords(records);               // records は Promise オブジェクトになってしまう
  setIsLoading(false);
}

こういうミスはコンパイルエラーにならないし、実行時もすぐにはおかしな挙動が出ないので気づきにくいです。ESLint のルールで自動検出できないかなと思って調べたので、まとめておきます。

require-await じゃなくて no-floating-promises

最初 require-await というルールを使えばいいのかなと思ったんですが、これは別物でした。

ルール 検出対象
@typescript-eslint/require-await async 関数の中に await が1つもない
@typescript-eslint/no-floating-promises Promise を返す関数を await せずに呼び出している

今回やりたい「呼び出し側の await し忘れ」の検出には no-floating-promises が正解です。

設定方法(flat config)

no-floating-promises は型情報を参照するルールなので、parserOptions の設定が必要です。これがないとルールが機能しないので要注意。

// eslint.config.js
export default defineConfig([
  {
    files: ['**/*.{ts,tsx}'],
    extends: [
      js.configs.recommended,
      tseslint.configs.recommended,
    ],
    languageOptions: {
      ecmaVersion: 2020,
      globals: globals.browser,
      parserOptions: {
        projectService: true,
        tsconfigRootDir: import.meta.dirname,
      },
    },
    rules: {
      "@typescript-eslint/no-floating-promises": "error",
    },
  },
])

ルールの severity は "error" / "warn" / "off"(または 2 / 1 / 0)です。"on" は無効な値なので効きません(ハマりました)。

エラーの例

設定すると、冒頭のコードはこんな感じでエラーになります。

async function onClickRefresh() {
  setIsLoading(true);
  
  const records = await fetchAllRecords();
  // ↑ ❌ Promises must be awaited, end with a call to .catch,
  //      end with a call to .then with a rejection handler
  //      or be explicitly marked as ignored with the `void` operator.
  
  setRecords(records);
  setIsLoading(false);
}

エラーメッセージがわりと親切で、「await するか、.catch() をつけるか、void で明示的に無視するかしてね」と教えてくれます。

await できないときは void 演算子を使う

useEffect のコールバックは async にできないため、中で非同期関数を呼ぶときは await が使えません。そういう場合はエラーメッセージにもある通り void 演算子を使います。

useEffect(() => {
  void fetchAll(); // ✅ 意図的に Promise を捨てていることを明示
}, []);

void は式の評価結果を常に undefined に変換する演算子です。fetchAll() 自体は実行されます。「この Promise は意図的に無視する」という意思表示として使うと、no-floating-promises のチェックを通過できます。

recommendedTypeChecked を使う方法もある

実は no-floating-promisestseslint.configs.recommendedTypeChecked に含まれているので、このプリセットに切り替えることでも有効にできます。

// eslint.config.js
export default defineConfig([
  {
    files: ['**/*.{ts,tsx}'],
    extends: [
      js.configs.recommended,
      // tseslint.configs.recommended,        // ← 削除
      tseslint.configs.recommendedTypeChecked // ← 追加
    ],
    // ...
  }
])

ただし、このプリセットは型情報を使う厳しめのルールがまとめて有効になります。たとえば Supabase の自動生成スキーマファイルに no-redundant-type-constituents でエラーが出たりするので、既存プロジェクトに一気に入れるのは注意が必要です。個別に no-floating-promises だけ追加するほうが影響範囲を把握しやすいかもしれません。

含まれているルールの一覧は typescript-eslint の GitHub で確認できます。

まとめ

  • await し忘れの検出には @typescript-eslint/no-floating-promises を使う
  • 型情報が必要なので parserOptionsprojectService: true)の設定が必須
  • useEffect など await できない文脈では void 演算子で明示的に無視できる
  • tseslint.configs.recommendedTypeChecked に含まれているが、他の厳しいルールも一緒に有効になるので注意
  • 設定変更後は ESLint: Restart ESLint Server で反映

地味なミスを自動で拾えるようになったので、入れておくと安心感があります。

参考リンク

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?