patorashのブログ

方向性はまだない

Dartの非同期処理のエラー制御にはZoneを使う

前回はgrinderを使ってタスクを実行するという記事を書きました。

その時に、compileタスクを定義しました。dartをjsに変換、sassをcssに変換、rspを変換などです(rspはrikuloのテンプレート機能。JSPみたいなやつ)。

@Task()
void compile() {
  dart2js(new Directory(path.absolute('web/client/js')));
  sass2css(new Directory(path.absolute('web/client/css')));
  compileRsp(new Directory(path.absolute('web/client')));
}

実はこれらを定義してちゃんとコンパイルが通るようになって、いい気になっていたのですが、欠陥がありました。例えば、sassファイルが正しくない状態でコンパイルされたらコンパイルエラーが発生して、grinderで実行中のタスクごと落ちてしまうという不具合です(しかもrikulo streamのプロセスは生きたまま…)。

つまりはエラー制御をしっかりやれって話です、はい。

エラーになったコード

エラーになったコードを書いておきます。 やっていることはシンプルで、非同期でsassをcssコンパイルしています。

/// sassをcssにコンパイルする
/// 
/// [file]がDirectoryだった場合は、再帰的に呼び出す。
/// Fileだったら非同期でコンパイルする。
void sass2css(FileSystemEntity file) {
  file.exists().then((bool exists) {
    if (file is Directory) {
      Directory dir = file;
      dir.list().listen((FileSystemEntity fse) {
        sass2css(fse);
      });
    } else if (file is File &&
              (file.path.endsWith('.scss') || file.path.endsWith('.sass'))) {
      // この処理でコンパイルに失敗したら本体を巻き込んで落ちる!
      new File("${path.withoutExtension(file.path)}.css").writeAsString(
        sass.compile(file.path),
        mode: FileMode.WRITE_ONLY
      );
    }
  });
}

エラー制御を追加したコード

Dartの非同期処理のエラーハンドリングにはZoneという概念があり、エラー制御はそのZoneの中で行うことができます。今回はそれをしていなかったために、全体を巻き込んで落ちていました。シンプルなZoneの例だと、非同期処理をrunZonedでくくります。

runZoned(() {
  // 非同期処理
}, onError: (e) {
  // 非同期処理でエラー発生したら実行されるブロック
  // なんらかのエラー制御を行う
});

これを元に、エラーになっていたコードを直します。

/// sassをcssにコンパイルする
/// 
/// [file]がDirectoryだった場合は、再帰的に呼び出す。
/// Fileだったら非同期でコンパイルする。
void sass2css(FileSystemEntity file) {
  file.exists().then((bool exists) {
    if (file is Directory) {
      Directory dir = file;
      dir.list().listen((FileSystemEntity fse) {
        sass2css(fse);
      });
    } else if (file is File &&
              (file.path.endsWith('.scss') || file.path.endsWith('.sass'))) {
      runZoned(() => new File("${path.withoutExtension(file.path)}.css").writeAsString(
          sass.compile(file.path),
          mode: FileMode.WRITE_ONLY
        ), onError: (e) {
        log(e.toString());
      });
    }
  });
}

エラーが起きたらその原因をgrinderのログに出力するように変更しました。 これで、コンパイルエラーが起きてもgrinderのタスクを巻き込んで落ちることはなくなりました。

編集後記

非同期処理のエラー制御まで頭が働いていませんでした。たしかにそれでアプリケーション自体を巻き込んで落ちてしまったら、ウェブサーバーとかの場合は洒落になりません…。しかしrunZonedを使えば見通しよくエラー制御できるのでいいですね。