みなさんは、forEach関数(メソッド)は好きですか? 私は昔LispをやっていたこともありforEachやmap関数が大好きです。for文はめったに使わないです。
const a = [1, 3 ,5 ,7];
for (const n of a) {
console.log(n * 3);
}
と
const a = [1, 3 ,5 ,7];
a.forEach(n => console.log(n * 3));
は等価です。
もう1つ
const a = [1, 3 ,5 ,7];
let total = 0;
for (const n of a) {
total += n;
}
console.log(total)
は
const a = [1, 3 ,5 ,7];
let total = 0;
a.forEach(n => total += n);
console.log(total);
は等価です。しかし、これはreduce関数を使うべきですね。
const a = [1, 3 ,5 ,7];
console.log(a.reduce((s, n) => s + n), 0);
node.jsでハマった
ある機能をnode.jsで作ろうとした時に、いつものようにforEach関数を使ってコードを書いてハマってしまいました。解決できたあとで考え直してみると当たり前なのですが同じようにハマる人もいそうなので書いてみます。
説明の都合で単純な例にしました、
- sqls配列にSQL文の文字列が入っています
- pg-promiseのnone()関数を使い、先頭から順番に実行します
- pg-promiseはPostgreSQLに接続しSQL文を実行するライブラリーです
- pg-promiseは非同期処理、Promise、async/awaitで処理を行います
const sqls = ["CREATE TABLE users(id SERIAL PRIMARY KEY, name VARCHAR(255))",
"INSERT INTO users(name) VALUES('山田太郎')"];
const db = ...省略...; // pg-promiseの初期化とデータベースをアクセスするインスタンスを作成
sqls.forEach(async(sql) => {await db.none(sql)});
と書いて、実行するとerror: relation "users" does not exist
エラーになってしまいました。
なぜ、これがエラーになるかというとasync(sql) => {await db.none(sql)}
無名関数内ではdb.none(sql)
の実行はawaitで終了を持ちますが、この無名関数自体はPromiseを作成しPostgreSQLにSQL文を送信したら直ぐに終了してしまいます。
したがってCREATE TABLE
文の実行が終了する前に、INSERT INTO
文の実行が始まってしまいます。そしてINSERT INTO
文の実行時点ではusersテーブルが無いのでエラーになってしまうのです。
解決策
pg-promiseのTaskにはbatch処理などもあり色々と試してみましたが解決しませんでしたが、一晩寝たら良い解決方法が思い浮かびました! for文を使えば良いのです😁
const sqls = ["CREATE TABLE users(id SERIAL PRIMARY KEY, name VARCHAR(255))",
"INSERT INTO users(name) VALUES('山田太郎')"];
const db = ...省略...; // pg-promiseの初期化とデータベースをアクセスするインスタンスを作成
(async() => {
for (sql of sqls) {
await db.none(sql)
}
})();
このコードではsqlsの1つの要素のSQL実行が終了してから次の要素の実行に移るので、上のようなエラーは発生しなくなりました。
まとめ
for文とforEach関数は同期処理の世界では等価ですが、非同期処理の世界では等価にならない事があります。
コードを書いていると、いつも得意なパターンを使って書いてしまいがちです。しかし世の中には等価のコードのパターンもあるので、それらを知り、対象のプログラムに最適なコードを選択できることが重要だと思いました。
映画マトリクスの続編が公開されるようですね楽しみです! そこで今回の画像はマトリクス風にしてみました。