Promiseでfinallyっぽいことをする


概要

Promiseでfinallyっぽいことをしたい。

でも、Promiseは、finallyと異なり、データやエラーが渡されるので、前の処理から渡された結果を、どうするのか、次の処理に何を渡すのか、エラーをどうするのかによって、finallyっぽいものの実装方法が何種類も考えられます。

さて、何らかのリソースをクローズしたり一時ファイルを削除するような処理の場合、クローズの結果そのものは、次の処理に渡す必要はなく、クローズの前のデータを次の処理に渡したいことが多いと思います。つまり、「前の処理の内容を、透過的に次の処理に渡す」、データの流れ的には一切手を付けず、finallyっぽい処理をすれば、使い勝手が良さそうです。

そこで、こんなコードを書いてみました。

コード

Promise.prototype.finally = function (promisable) {
    return this.then(
        function (originalResult) {
            return Promise.resolve(promisable())
                .then(function(result) {
                    return originalResult;
                })
        },
        function (originalReason) {
            return Promise.resolve(promisable())
                .then(
                    function (result) {
                        return Promise.reject(originalReason);
                    },
                    function (reason) {
                        reason.parent = originalReason;
                        return Promise.reject(reason);
                    }                
                )
        }
    )
}

使用例

doSomething(file)
  .finally(()=>{
     fs.unlink(file);
  })
 .then((result)=>{
      // finallyは無視して、doSomethingの結果を受け取る
  })
 .catch((cause)=>{
    // finallyを含めた、すべてのエラーを受け取る。二重にエラーだった場合は、parentに前のエラーを保持。
  })

補足

やっぱり、originalReason(「処理」の結果)を受け取りたいと考える人もいるかもしれませんが、その場合は、エラーとの分岐が必要で、それなら、普通にthenを書いて、受け取った結果を次に渡せば良いわけです。分岐が必要ないからこそ、一つの処理として書きたい。なので、ほとんどの場合、この実装で問題ないのではないかと思います。

問題

そもそも、Promiseの実装が多数あって、特定のライブラリが返すpromiseが、グローバルのPromiseのインスタンスではないことが多々あります。で、そういう場合には、Promise.resolve()してやらないといけない。これは、あまりエレガントではありません。

ついでに言うと、標準でもなんでもない拡張を、Promiseに加えるのは、品質管理的にあまり良くありません。ですので、これはあくまで実験的な機能として、頭の隅に置いておいてもらえればと思います。