node.jsのいろいろなモジュール18 – Domainでエラー処理をうまく扱う

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

非同期処理中のエラー処理

node.js用の以下のコードは、ファイル読込中になんらかのエラーが発生した場合、期待したとおりに動くでしょうか?

var fs = require('fs');

try {
    //ファイル読み込み処理
    fs.readFile("something-file", "utf-8", function (err, data) {
        if (err) {
            //エラーがあったら例外をスロー
            throw err;
        }
        console.log(data);
    });
} catch (err) {
    //エラー処理を行う
    console.error(e);
}

catchブロックでreadFileのエラーをハンドリングしてほしいところですが、
非同期コールバック中のエラーを受け取れないので、期待した結果になりません。

では、非同期処理中に発生したエラーはどのようにハンドリングすればよいのでしょうか。
そんなときに有用なのが、node v0.8から導入されたdomainモジュールです。
これはまだ実験的な位置づけのAPIのようですが、非同期処理を含むまとまった単位でエラーをハンドリングできる機能になっています。
ではDomainを使ってエラーハンドリングしてみましょう。

環境構築方法

今回使用した動作環境は以下のとおりです。
domainモジュールはnode標準のモジュールなので、npmでモジュールを追加インストールする必要はありません。

  • OS : MacOS X 10.7.4
  • Node.js : v0.8.15
  • npm : 1.1.66

サンプルプログラム作成

domainモジュールを使ってみる

下記がDomainを用いてエラー処理をしているサンプルコードです。
domainにバインドされたerrorイベントで処理することができます。

var fs = require('fs');
var domain = require('domain');
var d = domain.create();

d.on('error', function (e) {
    //run内で発生したエラーの処理
    console.error("domain:", e.message);
});

d.run(function () {
    //ファイル読み込み処理
    fs.readFile("something-file", "utf-8", function (err, data) {
        if (err) {
            //エラーがあったら例外をスロー
            throw err;
        }
        console.log(data);
    });

});

ドメインオブジェクト(変数d)とrun内で実行されている関数をバインドし、エラーが発生した場合、
ドメインオブジェクトのerrorイベントリスナで受け取っています。
なお、domainのバインディング方式は2つの種類があるので、それぞれ説明します。

Implicit Binding

明示的にAPIを使用せず、自動でオブジェクトやコールバックをDomainにバインドする方式を「Implicit Binding」といいます。
domain内でevents.EventEmitterを継承したコンストラクタを使用してオブジェクトを生成した場合と、
ドメイン内で非同期コールバック関数を使用した場合はImplicit Bingindによってdomainがバインドされます。
つまり、先ほどのraadFileの例では、非同期コールバック内ではImplicit Bindingでdomainにバインドされているわけです。

Explicit Binding

domainのbind関数を使用して、明示的にコールバック関数をバインドする方法を「Explicit Binding」といいます。
Implicit Bindingの場合、domain内のコールバックはすべてハンドリングされましたが、
Explicit Bindingの場合はハンドリング対象の関数を細かく指定することが可能です。
下記の例では、bind関数を使ってreadFileのコールバックをdomainにバインドしています。

var fs = require('fs');
var domain = require('domain');
var d = domain.create();

d.on('error', function (e) {
    //bind内で発生したエラーの処理
    console.error("domain:", e.message);
});

//ファイル読み込み処理
function myFunc(callback) {
    fs.readFile("something-file", "utf-8", d.bind(function (err, data) {
        if (err) {
            //エラーがあったら例外をスロー
            throw err;
        }
        console.log(data);
        callback();
    }));
};

myFunc(function () {
    console.log("myFunc callback");
});

このまま実行すると、readFileのコールバックでエラーが発生してthrowされ、domainのエラーリスナの処理が実行されます。
もし、readFileでなくmyFuncの先頭でエラーがthrowされた場合、エラーのハンドリングはされません。

コールバック関数が第1引数にエラーオブジェクトを取る場合、intercept関数を使用することができます。
この関数はコールバックの第1引数がエラーオブジェクトである場合、
そのオブジェクトが存在すれば自動でdomainにエラーイベントを発生させます。
さきほどの例で、readFileのコールバックを定義していた箇所を下記のように変更してみてください。
Errorをオブジェクトをthrowしていませんが、エラー発生時には同じように動作します。

・・・
    fs.readFile("something-file", "utf-8", d.intercept(function (data) {
        console.log(data);
        callback();
    }));
・・・

まとめ

今回はnodeの標準モジュール、Domainについて紹介しました。
これを使えば非同期処理のエラーハンドリングが楽にできますね。
Domainについては今後、仕様が変更となる可能性もあるので注意してください。

参考サイトなど