Node.js は、JavaScript のランタイム環境で、非同期モデルとシングルスレッドの統合が特徴です。同期・非同期の概念を理解することで、リアクティブで効率的なアプリを作成できるようになります。ここでは各概念をコード例を使って解説します。
1. 同期通信
同期通信とは、処理結果が返るまでプログラムが待機する効率的でも暗慮も要する方法です。
事例: サーバー内での計算タスク
console.log('計算を開始します');
function calculate() {
let sum = 0;
for (let i = 0; i < 1e9; i++) {
sum += i;
}
return sum;
}
console.log('計算結果:', calculate());
console.log('次のタスクを実行します');
実行結果
計算を開始します
計算結果: 499999999067109000
次のタスクを実行します
動作の流れ
- 計算が終わるまで次の処理は実行されない。
- 計算が重いと、サーバー全体が処理を待たされる。
問題点
大きなタスクがあると応答性が低下し、他のクライアントが待たされる。
2. 非同期通信
非同期通信は、処理を待たずに次の処理を進める方法です。
事例: APIリクエスト
console.log('APIリクエストを送信します');
setTimeout(() => {
console.log('APIリクエストが完了しました');
}, 2000); // 2秒後に実行
console.log('次のタスクを実行します');
実行結果
APIリクエストを送信します
次のタスクを実行します
APIリクエストが完了しました
動作の流れ
setTimeout
を使って非同期タスクを設定。- リクエストが完了するを待たずに次のタスクを実行。
利点
- 待ち時間を有効に活用。
- 他の処理を並行して実行可能。
3. 非同期I/O
ファイルやネットワーク操作を非同期で実行する方法です。
事例: ファイル読み込み
import { readFile } from 'node:fs';
console.log('ファイル読み込み開始');
readFile('example.txt', 'utf8', (err, data) => {
if (err) throw err;
console.log('ファイルの内容:', data);
});
console.log('次の処理を実行');
実行結果
ファイル読み込み開始
次の処理を実行
ファイルの内容: example.txt の本文内容。
example.txt の本文内容。
example.txt の本文内容。
example.txt の本文内容。
動作の流れ
readFile
で非同期にファイルを読み込むリクエストを送信。- ファイル読み込みが終わるのを待たずに次の処理を実行。
- ファイルの内容が読み込まれると、コールバック関数で処理が実行される。
利点
- ファイル読み込み中に別の処理が実行可能。
- 高速で効率的なリソース利用。
4. イベント駆動
イベント駆動では、特定のイベントが発生したときにのみ処理が実行されます。
事例: サーバーでのリクエスト処理
import { createServer } from 'node:http';
const server = createServer((req, res) => {
console.log('リクエストを受け取りました:', req.url);
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello World!');
});
server.listen(3000, () => {
console.log('サーバーがポート3000で待機しています');
});
実行結果(コンソール)
サーバーがポート3000で待機しています
リクエストを受け取りました: /
リクエストを受け取りました: /favicon.ico
実行結果(http://127.0.0.1:3000/)
Hello World!
動作の流れ
- サーバーは
req
(リクエスト)というイベントを待機。 - リクエストが来たときだけ、指定された処理を実行。
利点
- 必要なときだけ動作するので効率的。
- 他のリクエストを並行して処理可能。
5. マルチスレッド
マルチスレッドは、複数のスレッドでタスクを並行実行します。Node.jsは基本的にシングルスレッドですが、worker_threads
モジュールを使うことでマルチスレッドを利用できます。
事例: 重い計算をマルチスレッドで実行
import { Worker } from 'node:worker_threads';
console.log('メインスレッド開始');
const worker = new Worker(`
const { parentPort } = require('node:worker_threads');
let sum = 0;
for (let i = 0; i < 1e9; i++) sum += i;
parentPort.postMessage(sum);
`, { eval: true });
worker.on('message', (sum) => {
console.log('計算結果:', sum);
});
console.log('他の処理を実行');
実行結果
メインスレッド開始
他の処理を実行
計算結果: 499999999067109000
動作の流れ
- メインスレッドでWorkerを作成。
- 重い計算をワーカースレッドで実行し、結果をメインスレッドに通知。
- メインスレッドは計算中に他のタスクを実行可能。
利点
- 重い処理がメインスレッドをブロックしない。
- 高いパフォーマンス。
6. シングルスレッド
Node.jsはシングルスレッドモデルを採用しており、1つのスレッドでイベントループを管理します。ただし、非同期I/Oやマルチスレッド(オプション)により並列処理が可能です。
事例: 複数のリクエストをシングルスレッドで処理
import { createServer } from 'node:http';
const server = createServer((req, res) => {
setTimeout(() => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello World!\n');
}, 2000); // リクエストごとに2秒待つ
});
server.listen(3000, () => {
console.log('サーバーがポート3000で待機しています');
});
実行結果(コンソール)
サーバーがポート3000で待機しています
実行結果(ブラウザ)・・・表示まで2秒
Hello World!
動作の流れ
- 各リクエストを非同期に処理。
- 2秒の遅延を待つ間も、他のリクエストを処理可能。
利点
- 非同期I/Oにより複数のリクエストを効率的に処理。
- 軽量でスケーラブル。
まとめ: Node.jsのモデルが持つメリット
- 同期通信はシンプルだが効率が悪い。
- 非同期通信や非同期I/Oにより、待ち時間を有効活用可能。
- イベント駆動で効率的に処理を進める。
- マルチスレッドを利用して、重い処理を分離可能。
- シングルスレッドでイベントループを活用し、高スループットを実現。
Node.jsの非同期モデルを理解すれば、リアクティブで効率的なアプリケーションを作れるようになります。ぜひ、これらのコードを試しながら学んでください! 😊