#Overview
過去にバグになってしまったものを忘れないよう書き留めておくシリーズです。
今回の題材はArray.prototype.forEach()
です。以下は私が当時のコードを思い出しながらさらり書いたコードです。(動作未確認)
複数のサイトのHTMLを取得して、ファイル出力しています。
import fs from "fs";
const fsPromises = fs.promises;
const writeFile = async (filename, json) => {
const callback = err => { if (err) console.error(err); };
await fsPromises.writeFile(filename, JSON.stringify(json), callback);
}
async function crawl() {
const result = {};
["https://www.google.co.jp/", "https://www.yahoo.co.jp/"].forEach(async (url) => {
const res = await fetch(url);
result[url] = await res.text();
})
await writeFile("urls.json", result)
console.log("result:", result);
}
crawl();
#Target reader
- この結果がわからない方
#Prerequisite
- JavaScriptを一通り理解している
- 出力結果はChromeで実行したが、環境によって出力内容が微妙に異なることがあることに注意。
- 実行環境はNode V10系。(2020年7月時点で既にちょい古め)
#Body
##答え合わせ
正解はこうなる。
> "result:" Object { }
何も出ないが正解
出力されたファイルの中身も同様に空のオブジェクト。
どうして何もないっていないかというと、forEach
でawaitがないため。
awaitがないということはawaitの部分でファイル出力の方に処理が進んでしまう。
ではawaitいれればと思うが、実はこのawaitは意味がない。
詳しくは@frameairさんが書かれている記事を参照してください。
https://qiita.com/frameair/items/e7645066075666a13063
解決策はPromise.all()
、もしくは一つずつ処理しないといけない場合はfor...of
で一つずつawaitすればいい。
Promise.all()
はエラーの際に扱いにくいため、Promise.allSettled()
がこれからはよさげ。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled
私がforEach
を嫌いになった理由は、非同期処理があるかないかで書き方を変更しないといけないのが面倒だから。
極めつけとしては、asyncを入れても実行時エラーにならないのがつらい
ESLintで静的チェック、もしくはテストコードでカバーしておけばいいといえばそれまでですが
#Conclusion
forEach
はawaitできないため、非同期処理を何気なく追加したらバグになる可能性がある!
forEach
を利用するなら、非同期処理が入ったときにPromise.all()
、Promise.allSettled()
もしくはfor...of
に切り替えること。