はじめに
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-promises は tseslint.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を使う - 型情報が必要なので
parserOptions(projectService: true)の設定が必須 -
useEffectなどawaitできない文脈ではvoid演算子で明示的に無視できる -
tseslint.configs.recommendedTypeCheckedに含まれているが、他の厳しいルールも一緒に有効になるので注意 - 設定変更後は
ESLint: Restart ESLint Serverで反映
地味なミスを自動で拾えるようになったので、入れておくと安心感があります。