ソフトウェアエンジニア、Neil Lobo氏
Node.jsの人気の秘密は、イベントループにあります。Node.jsは、従来のフレームワークのようなマルチスレッドのアプローチではなく、イベントドリブンでノンブロッキングなI/Oモデルを採用しています。これにより、メモリの使用量が減り、より軽量で効率的になり、データ量の多いリアルタイムアプリケーションにも対応できるようになりました。
このイベントドリブンでノンブロッキングなアプローチは、I/Oやネットワークアクティビティがボトルネックとなるアプリケーションに適しています。逆に、CPUを酷使するようなアプリケーションでは、Node.jsはあまり適していないと言えるでしょう。
しかし、素のNode.jsを扱うには、非同期のコールバックを使用する必要があり、悪名高いコールバック地獄に陥ります。これは、フォローするのが困難なロジックにつながります
//Callback Soup/Spiral Callback of Doom
function doAsync1(function () {
doAsync2(function () {
doAsync3(function () {
doAsync4(function () {
//finally do something });
});
});
});
エラー処理やネストされたコールバックは書くのが面倒ですし、その存在がコードの保守や拡張を難しくしています。これらの問題を克服するためにいくつかのテクニックがありますが、最もポピュラーなのはFibersとPromisesです。 MeteorはFibersを使ってイベントループ上で同期的なコードを実現しているので、今回は前者について説明したいと思います。
ファイバー
馴染みのない方もいらっしゃるかもしれませんが、Fibersは協調的マルチタスクのモデルを使用するコンピュータサイエンスの概念です(スレッドがプリエンプティブなマルチタスクを使用するのとは異なります)。
スレッド化されたコードは、他のスレッドで実行されているコードにCPUサイクルを与えるために、式の評価中であっても、どの時点でも中断される可能性があります。ファイバーでは、このような中断やコンテキストスイッチは、CPUや下位のプロセスによって決定されるのではなく、プログラマーによって決定されます。プログラマーは、自分のコードがどこで降伏し、他のファイバーにCPUサイクルを与えるかを決定します。
ここでは、結果が渡されるcallbackFunctionを使用する典型的なNode.jsコードの例を示します。
getDataFromDisk = function(fileName, key, callbackFunction) {.
var result;
fs.readFile('/path/fileName', function(err, res) {
if (err) console.log(err); else {
result = transform(res, key);
callbackFunction(result);
}}};
これをもっと同期的なスタイルで書いてみましょう。
getDataFromDisk = function(fileName, key) {
var result;
fs.readFile('/path/fileName', function(err, res) {
if (err) console.log(err); else result = transform(res, key);
}
return result;// 常に未定義となります。
};
// getDataFromDiskを使用して値を返し、次にそれを印刷しようとします。
// すべて同期して出力します。
var result = getDataFromDisk('helloWorld',key);
console.log(result);
// 未定義
上記のコードは、イベントループがコールバックの結果を待たずに、結果を返す行(*)に進んでしまうため、常に未定義を返します。
繊維の利用
ファイバーとは、コンテナ関数の一種であり、プロセス全体をブロックすることなく、I/Oやネットワークアクティビティの待ち時間にサブルーチンをブロックするために使用できる。
var Fiber = Npm.require('fibers');// getDataFromDisk function using Fibers
getDataFromDisk = function(filename, key) {var fiber = Fiber.current; //get the current Fiber
fs.readFile('/path/fileName', function(err, res) {
if (err) console.log(err); else{
/* Resume execution of this fiber. What’s passed to fiber.run will become the value returned by Fiber.yield below */
fiber.run( transform(res, key) );
var result = Fiber.yield();
return result;};
// Finally we wrap our code in a Fiber, then run it
Fiber(function() {
var result = getDataFromDisk('helloWorld', key);
console.log(result);
}).run();
これで、見た目は同期的なコードでも、イベントループでは非同期的に実行されていることになります。
ファイバーについての詳細はこちらをご覧ください。https://github.com/laverdet/node-fibers)
