概要
Node.jsはシングルスレッドかつ非同期な実行環境であり、その特性を理解し、効果的に利用するためには、クロージャ、タイマー制御、イベント駆動、非同期処理、Promise、await/async、コールバック、ウォーターフォールモデルなどの概念を理解する必要があります。
簡単な紹介
0. クロージャ (closure)
クロージャとは、関数が定義されたスコープ(最も近い花括弧内)のデータをプライベート化する仕組みです。通常、花括弧内の変数は互いにアクセスできますが、花括弧外からのアクセスはできません。例えば、ネストされた関数が外側の関数の変数を参照できる一方で、逆は不可能です。
なぜクロージャが必要かというと、Node.jsのシングルスレッド環境では、varで宣言した変数は予め解析されますが、代入は即座に行われません。これにより、代入順序によってundefinedエラーが発生することがあります。また、function宣言は一度に行われるので、内部関数が外部関数の変数を参照できるようにすることでクロージャが形成されます。クロージャは関数のカリー化、デバウンス、スロットリングなどに使用されます。
1. タイマー
setTimeoutの基本的な使用方法を示します。
setTimeout(() => {
console.log("setTimeout 5000");
}, 5000);
console.log("setTimeout test");
上記コードでは、「setTimeout test」がすぐに表示され、5秒後に「setTimeout 5000」が表示されます。
2. コールバック (callback) と非同期 (Async)
Node.jsの非同期プログラミングは主にコールバック関数によって実現されます。コールバック関数はタスクが完了したときに呼び出されます。Node.jsのAPIはすべてコールバックをサポートしています。
例えば、ファイル読み込み中に他の命令を実行し、読み込み完了時にコールバック関数にファイル内容を渡すことで、I/O操作によるブロックを回避できます。
以下は非同期操作の例です:
- タイマー
- ネットワーク接続の確立
- ネットワークストリームデータの読み取り
- ファイルへの書き込み
- Ajax送信
- データベースサービスへのリクエスト
非同期処理をより扱いやすくするために、async/awaitを使用してPromiseオブジェクトを返し、thenメソッドで結果を取得することができます。以下のコードは、この方法を示しています。
function longOperation(n) {
return new Promise(resolve => {
setTimeout(() => resolve(n + 2000), n);
});
}
async function processSteps() {
console.time("processSteps");
const time1 = 3000;
const time2 = await longOperation(time1);
const time3 = await longOperation(time2);
const result = await longOperation(time3);
console.log(`result is ${result}`);
console.timeEnd("processSteps");
}
processSteps();
3. イベント (events)
イベントループは、Node.jsの非同期API(タイマー、I/O操作など)を異なるキュー(Timer, Poll, Check)に分けて管理し、同期コードの実行後、イベントループがこれらのキューから非同期APIを処理します。
EventEmitterはイベントモジュールであり、リスナー登録順にイベントが処理されます。emitメソッドはデフォルトで同期的に実行されますが、非同期処理を行う場合はnextTick内で呼ぶことが推奨されます。
const events = require('events');
const eventEmitter = new events.EventEmitter();
eventEmitter.on("start", handleStart);
eventEmitter.on("data", handleData);
function handleStart() {
console.log("start handler");
eventEmitter.emit("data");
}
function handleData() {
console.log("data 2018");
}
eventEmitter.emit("start");