はじめに
現在Expressのエラーハンドリングについて勉強しており、その為に公式ドキュメントを閲覧していました。
「デフォルトのエラー処理ミドルウェア関数は、ミドルウェア関数スタックの最後に追加されます。」という記述があり、この「ミドルウェア関数スタック」が原因で頭を悩ませることになりました。
概要だけご説明すると「スタック」の意味が分からないという話です。
Expressのミドルウェアについて
※本題からずれるのでExpressのミドルウェアとは何かなどの説明は省略します。
app.jsが読み込まれたときにuseメソッドやmethod(get,post,put,delete)メソッドによりミドルウェア関数がスタックに追加されます。そして、登録されたミドルウェアはスタックに追加された順に実行されます。
const express = require('express');
const app = express();
/*
このように3つのミドルウェアをuseメソッドに渡した場合、リクエストのたびに
「最初のミドルウェア」
「2個目のミドルウェア」
「3個目のミドルウェア」
の順にコンソールに出力される。
*/
app.use((req, res, next) => {
console.log('最初のミドルウェア');
return next();
});
app.use((req, res, next) => {
console.log('2個目のミドルウェア');
return next();
});
app.use((req, res, next) => {
console.log('3個目のミドルウェア');
return next();
});
そして、エラー処理を行うミドルウェアをスタックに追加する際は他のミドルウェアより後ろの方で追加をしないとエラーハンドリングができない仕組みです。
つまり、自作のエラー処理ミドルウェアより後にスタックに追加されたミドルウェア内でエラーが発生しても自作のエラー処理ミドルウェアで対応することはできません。
そのため、Expressデフォルトのエラー処理ミドルウェアにてエラーハンドリングが行われます。
const express = require('express');
const app = express();
/*
このように3つのミドルウェアをuseメソッドに渡した場合、リクエストのたびに
「最初のミドルウェア」
「2個目のミドルウェア」
の順にコンソールに出力される。
*/
app.use((req, res, next) => {
console.log('最初のミドルウェア');
//処理Aがここで行われるとする
//処理Aでエラー発生時は自作エラー処理ミドルウェアにてエラーハンドリングを行う
return next();
});
app.use((err, req, res, next) => {
//自作エラー処理ミドルウェア
const { status = 500, message = 'エラー発生' } = err;
res.status(status).send(message);
});
app.use((req, res, next) => {
console.log('2個目のミドルウェア');
//処理Bがここで行われるとする
//処理Bでエラー発生時はデフォルトのエラー処理ミドルウェアにてエラーハンドリングを行う
return next();
});
「スタックの最後にデフォルトのエラー処理ミドルウェアが追加される」ことによりデフォルトのエラー処理ミドルウェアは最後に実行されるということです。
スタック=データ構造のスタックではない
最初は「ミドルウェア関数スタックの"スタック"=データ構造のスタック」と認識していました。
スタックは「LIFO(Last In First Out)」の規則でデータを出し入れするデータ構造なので
その場合一番最後に追加されたものは一番最初に取り出されて、実行されるのではないかと思ったわけです。
しかし、デフォルトのエラー処理ミドルウェアが最後に追加されて最後に実行されるという事は
「FIFO(First In First Out)」の規則であるためどういうことなのか頭を悩ませていました。
デバッグモードにてexpressのstack変数(そこにスタックに追加した関数が格納されている)の中身を見た結果
インデックスの0番地の中身=一番最初に追加したミドルウェアであり、その後のミドルウェアを順に実行する処理もインデックスを1つずつ増やしていく形でstack変数を利用していたのでやはりデータ構造としてはFIFOっぽいです。
要するに「ミドルウェア関数スタックの"スタック"=データ構造のスタック」は誤りという事になります。
スタック=コールスタックの事?
「スタック」の他の使われ方として、「コールスタック」の事を示す場合もあるそうです。
関数の呼び出しに関することなので結構有力候補だったのですが、結局のところ「コールスタック」も積み上げて取り出してを繰り返すLIFOですので、「スタック=コールスタック」でもなさそうです。
結局スタックが何なのかは分からなかった
他にも自分の知らない意味をスタックが持っているのかと思い調べてみましたが
- ぬかるみ、雪などにはまって、自動車が立ち往生すること。
- 物事に行き詰る事
など関係なさそうなものが多かったです。
「技術スタック」など「スタック=蓄え」という意味で使う場合もあるらしいですが
これは本来スタックではなくストックなのでは・・・と心の中では思っています。
ただ、「スタック=蓄え」なら「ミドルウェア関数の蓄え」ということになり、納得は出来ます。
結論
スタックというよりキューだと思うし、キューじゃなければストックじゃないのかと思いました。
「ミドルウェア関数スタック=ミドルウェア関数を貯めておく場所」という認識で勉強を進めていこうと思います。