音の鳴るブログ

鳴らないこともある

Promiseの使い方が良く分からなくなってきた

先に言っておくと疑問文で終わる雑な記事です。

Promise、正常系の処理は比較的簡単に書けるのだけど、何か間違いがあったときに Promise 自体のエラーハンドリングが強力すぎて良く分からなくなる。

readFile というファイルを読み込む Promise ベースの良くできた関数があるとして、

読み込んだファイルを処理するとき

readFile("foo.txt").then(function(text) {
  doSomething(text);
});

ファイルが読み込めない場合を想定するとき

readFile("not-found.txt").then(function(text) {
  doSomething(text);
}, function() {
  readFailed();
});

この時、catch を使うのは良くなくて、ファイルの読み込みは成功したのに readFailedが実行される場合が考えられる。

readFile("存在するファイル.txt").then(function(text) {
  doBrokenSomething(text); // ここでエラーが発生する
}).catch(function() {
  readFailed();
});

これを回避するにはこう書く。というか Promise ベースの API を使ったら最後に必ず catch しておく必要があると思う。

readFile("foo.txt").then(function(text) {
  doComplexSomething(text); // ここでエラーが発生するかも知れない
}, function() {
  readFailed();
}).catch(console.error.bind(console));
// ^^ doComplexSomething/ readFailed でエラーがあったとき

と、いうのが Promise の基本的な使い方だと思っているのだけど、、

これだとエラーがあったことは分かるけど、どこでエラー発生したか等の情報がいまいち分かりにくくて、デバッグしづらい。よくよく考えると Promise は非同期で実行されるフローを分かりやすく書くことが目的なので、すべてのおぜん立てが揃った最後の then の中で複雑な処理を書くのがそもそもの間違いな気がする。その段階では Promise のエラーハンドリング機能は邪魔でしかないと思う。Promise の呪縛から逃れるには 大きなスコープ を使うか setTimeout を使うというのが考えられるのだけど、本当にこんなので良いのでしょうか?

// 大きなスコープを使う
var text = null;

readFile("foo.txt").then(function(_text) {
  text = _text;
});

button.on("click", function() {
  if (text !== null) {
    doComplexSomething(text);
  } else {
    readFailed();
  }
});
// setTimeout を使う
button.on("click", function() {
  readFile("foo.txt").then(function(text) {
    setTimeout(function() {
      doComplexSomething(text);
    }, 0);
  }, function() {
    setTimeout(function() {
      readFailed();
    }, 0);
  });
});

追記:

ということをブツブツ書いていたら、有益な情報教えていただきました!!