こんにちは、最近業務とテニスを盾に更新が滞ってました。ダブルシールドです。え、片方がシールドになってない???そういえば両手に剣を持ってる戦士はたまにいますが、両手に盾持ってる戦士はあまりいませんね。
さて、今日はJava Script(node)を書いてるとやらかしがちなawait書き忘れの防ぎ方について書きます。await書き忘れ問題防ぎたいけど、TypeScriptよく知らないけど、でも手っ取り早く(そしてざっくり)内容掴みたいというニッチな人向けのピンポイント記事。と言うか自分用メモ。
1. await書き忘れ問題
await、書き忘れると厄介ですね。ランタイムエラーが出ない割に、予想できない振る舞いをするわ、見つけにくいわで大変です。詳しくはこちらの方が書かれてます。「デフォルトはawaitにして、noawait的なキーワードを作った方がいいでしょ」って思いますね。1残念ながら、当面実装されなさそうですが async/await: nowait keyword?
2. await 書き忘れ問題の防ぎ方3つ
さて、答えは簡単、静的解析的なアプローチです。具体的には、次の3つ
- @typescript-eslint/no-floating-promises
- @typescript-eslint/no-misused-promises
- そこそこ正しい型づけを使ったTypeScriptによるコンパイル
ESLint(静的解析ツール)をまだやってない方は、この際ESLintも始めましょう(丸投げ)。もしQiita記事を1つ選ぶなら、私はこちらが好みです。
それから、安心してください、1と2はTypeScript(Java Scriptに型がついた言語、たぶん)へ移行しなくても効果が得られます。2今はまだ、、、たぶん今はまだその時やないんや。。。!それぞれざっくり説明します。3余談。もともとTSLintというTypeScript向けの静的解析ツールがあり、1と2はTSLintのルールでした。しかし、TSLintはDeprecatedになり、ESLintへの統合が進んでます。TypeScript向けに作られてるので、TypeScript使ってる前提でしか動かないように思いますが、普通にチェックかけれました。TypeScriptには、Type Checking JavaScript Filesなる機能があり、おそらくそれで型推論されてる?よくわからんがまぁ動いてるから、ヨシ!
1. @typescript-eslint/no-floating-promises
@typescript-eslint/eslint-plugin というESLintのプラグインがあるのですが、これはその静的解析ルールです。これを有効化して静的解析(e.g. $ npx eslint ...
)すると、こういう感じのパターンを検知します。
async function bar() {
// なんかこう、asyncなことをする
}
async function foo() {
bar(); // @typescript-eslint/no-floating-promises Error
}
名前の如く、宙に浮いたpromiseを禁止するルールですね。詳しくはこちら。
余談: eslintの警告を無視したい
- 意図的にawaitをつけたくない場合は、対象行の先頭に
void
とつけるとESLintの警告が出なくなります - あるいは、特定行を無視するESLint用のコメントを書くでも良いかと思います。
2. @typescript-eslint/no-misused-promises
これも同じく@typescript-eslint/eslint-plugin の静的解析ルールです。これを有効化して静的解析(e.g. $ npx eslint ...
)すると、こういう感じのパターンを検知します。
async function bar(){
// なんかこう、asyncなことをする
return false;
}
async function foo() {
if (bar()) {} // @typescript-eslint/no-misused-promises Error
}
詳しくはこちら。自分で書いといてあれですが、こういう使われ方するんですね。
3. TypeScriptのそこそこ正しい型づけ
4^ 筆者のTypeScriptに対する自信の度合いに関する機敏を表現しております TypeScriptで、関数や変数の型づけが必要でコンパイル(e.g. $ npx tsc ...
)しないとわからないパターンです。具体的には次のとおりです。
async function bar(): Promise<void> {
// なんかこう、asyncなことをする
return false;
}
async function foo() {
const result: boolean = bar(); // .ts -> .jsにコンパイルした時に次のようなエラーがでる
// Type 'Promise<void>' is not assignable to type 'boolean'.
}
1および2と異なり、TypeScriptの(雑な)型づけを行ってます。
ただし、Java Scriptで同等のことを実装してしまっても、気付く可能性はわりと高いです。(resultにPromise的な変数が入ってくるので、後段の処理で多分気付く)。なので、わざわざESLintとしても静的解析ルールは必要ないのかなと。
余談1 console.log
console.log の引数型はAny(どんな型でもブッ込める、TypeScriptを実質的に無効化する型)。そのため、これにPromiseの結果を直接ブッ込むとおそらくawaitの書き忘れを検知できない。
余談2 tscコマンドの使い方
- tscコマンドは ファイル指定せずに実施する方が良い。さもないとこう言う感じの謎エラーに見舞われる
src/main.ts:14:3 - error TS2585: 'Promise' only refers to a type, but is being used as a value here. Do you need to change your target library? Try changing the `lib` compiler option to es2015 or later.
14 Promise.all([f(), p]); // no-floating-promises Error because of .all
~~~~~~~
3. その他、TypeScriptの細かいメモ走り書き
クラスの文法
- TypeScript的な(?)クラスの書き方がある模様。これに従うと await書き忘れを検知できるはず
- 最近触ってるJava Scriptのクラスはこちらの記事の感じで作ってます。(そもそもJava Scriptはプロトタイプベースの言語なので、オブジェクト指向的な文法はもともとなかった)
import/exportの文法
- モジュール間でawait書き忘れを検知するにあたり、正しく(?)importをする必要がある
- https://www.typescriptlang.org/docs/handbook/modules.html#testts-2
外部モジュールのawait忘れ検知
- おそらくこの辺のmoduleを使って型づけする必要がある(あまりちゃんと調べられてない)
- https://github.com/DefinitelyTyped/DefinitelyTyped (おそらく中央管理されてるやつ)
- https://www.npmjs.com/package/@types/request-promise (その一例)
4. まとめ
猛ダッシュした割に長くなりましたが以上です。もともとawait書き忘れを防ぎたい一心でいろいろ調べた結果、TypeScriptをよく知らないばかりに(今もそんなにわかってない)やたら苦労したのでまとめることにしました。5余談ですが、@typescript-eslint/no-misused-promises ルールが思ってたとおり動かなくて、わからなさすぎて、typescript-eslintのコミュニティに質問投げたりもしました。聞いた場所が正しかったか今もよくわかってないんですが、わざわざコードまで見てくれて、めっちゃアドバイスくれました。最高かよ
ちなみに、「3. TypeScriptのそこそこ正しい型づけ」に関して、TypeScript必要と述べましたが、実はいらないかもです。TypeScriptにはJSDocから型を特定する機能があるので、JSDocを書けばうまくいくかも。試してないので、知ってる方教えてくだされ
5. 参考
- async/await: nowait keyword?
- Disallow calls to async functions from async functions without using ‘await’
6. おもちかえり
- https://github.com/mwakizaka/detect_missing_await
- await書き忘れがどの程度防げそうか確認できます
- .eslintrc.jsやtsconfig.jsonはわりと雑に作ったので、ほどほどに参考にしてくだしあ