node.jsで使える非同期コントロールフローライブラリ nue その9 - 0.2.0リリース

0.2.0をリリースしました。npm install nue でインストールできます。

CHANGELOG
  • 非同期コールバックに渡されるエラーはNueAsyncErrorでラップして通知しデバッグしやすくしました。
  • ハンドルされなかったエラーはNueUnhandledErrorでラップして通知しデバッグしやすくしました。
  • flowに名前を付けられるようにしました。
  • step関数(flowに渡される関数)内で、this.flowNameを使用できるようにしました。
  • step関数内で、this.stepNameを使用できるようにしました。
  • step関数内で、this.end関数が最初の引数でエラーオブジェクトを受け取らなくなりました。エラーでflowを終了したい場合は単にそのエラーをthrowしてください。


以下、代表的なものについて簡単に説明します。

非同期コールバックに渡されるエラーのハンドリング


非同期コールバックに渡されるエラーの問題点は、発生個所がわかりにくいことです。nueでは、flowやflowの内側の関数(stepと呼びます)の名前をエラーに含めることで、エラーの発生個所を特定しやすいようにしました。

例として次のコードを考えます。flow関数の呼び出しに'readFileFlow'という文字列を渡していますが、これがflowの名前です。名前は必須ではないのですが、これがあるとデバッグしやすくなります。エラー情報にこの名前を使うからです。(名前がない場合は単に空文字が使われる)。それから、同じ理由でstepにも名前をつけています。

var flow = require('nue').flow;
var fs = require('fs');

var myFlow = flow('readFilesFlow')(
  function readFiles(file1, file2) {
    fs.readFile(file1, 'utf8', this.async());
    fs.readFile(file2, 'utf8', this.async());
  },
  function concat(data1, data2) {
    this.next(data1 + data2);
  },
  function end(data) {
    if (this.err) throw this.err;
    console.log(data);
    console.log('done');
    this.next();
  }
);
myFlow('file1', '存在しないファイル名');


このflowの定義をmyFlowという変数で受けて、2番目のパラメータに存在しないファイル名を渡して実行します。存在しないファイルを読もうとするので実行結果はエラーになりますが、そのときの出力内容はこうなります。

NueAsyncError: cause = [Error: ENOENT, no such file or directory '存在しないファイル名'], 
flowName = 'readFilesFlow', stepName = 'readFiles', stepIndex = 0, asyncIndex = 1
    at /Users/toshihiro/WebstormProjects/nue/lib/nue.js:194:39
    at [object Object].<anonymous> (fs.js:88:5)
    at [object Object].emit (events.js:67:17)
    at Object.oncomplete (fs.js:1058:12)


まずは、最初のNueAsyncErrorという記述で非同期コールバックにエラーが渡されたことを示します。それから各情報は次のとおりです。

  • cause: 非同期コールバックに渡されたエラー
  • flowName: flowの名前
  • stepName: stepの名前
  • stepIndex: stepがflowの中において何番目に定義されているか
  • asynIndex: stepの中において何回目のthis.asyncが返すコールバックで発生したか

this.flowNameとthis.stepName

こんな使い方ができます。ちょっとした挙動確認(ちゃんとstepが呼ばれているか?とか)に便利です。

var flow = require('nue').flow;

function log() {
  console.log(this.flowName + '.' + this.stepName);
}

var myFlow = flow('myFlow')(
  function step1() {
    log.call(this);
    this.next();
  },
  function step2() {
    log.call(this);
    this.next();
  },
  function step3() {
    if (this.err) throw this.err;
    log.call(this);
    this.next();
  }
);
myFlow();


出力はこうなります。

myFlow.step1
myFlow.step2
myFlow.step3

this.end関数の仕様変更


これまでは、次のような書き方ができました。

var flow = require('nue').flow;

var myFlow = flow(
  function step1() {
    this.end(new Error('hoge')); // step3へジャンプ。 step3のthis.errはここで渡したエラーオブジェクト。
  },
  function step2() {
    this.next();
  },
  function step3() {
    if (this.err) throw this.err;
    this.next();
  }
);
myFlow();


しかし、上記のサポートはやめて、エラーでflowを終了するときは単にthrow new Error()をしてもらうことになりました。this.endは正常系のflowの終了のみに使えます。

var flow = require('nue').flow;

var myFlow = flow(
  function step1() {
    throw new Error('hoge'); // step3へジャンプ。step3のthis.errはここでthrowしたエラーオブジェクト。
  },
  function step2() {
    this.next();
  },
  function step3() {
    if (this.err) throw this.err;
    this.next();
  }
);
myFlow();