NodeJSではPromiseを利用して非同期関数をカプセル化する.

4428 ワード

Node.jsを書く過程で、連続的なIO操作は「ピラミッドの悪夢」を引き起こすかもしれません.コールバック関数の多重ネストはコードを維持しにくくし、Common JsのPromiseを利用して非ステップ関数をカプセル化し、統一したチェーンAPIを使って多重エコーの悪夢から逃れることができます.
Node.jsから提供された非ブロッキングIOモデルは、IO操作をコールバック関数で処理することができますが、連続したIO操作が必要となると、あなたのコールバック関数が多重にはまり、コードが非常に不格好で、メンテナンスが難しく、多くのエラー処理が繰り返される可能性があります.いわゆる「Pyramid of Dom」です.
 
  
step1(function (value1) {
    step2(value1, function(value2) {
        step3(value2, function(value3) {
            step4(value3, function(value4) {
                // Do something with value4
            });
        });
    });
});
これは、Node.jsのControl flowの問題です.この問題に対して、解決策はたくさんあります.例えば、asyncやeventProxyなどを利用しています.
Promiseとは
CommonJsのPromise仕様にはいろいろな種類がありますが、私たちが一般的に検討しているのはPromise/A+仕様で、Promiseの基本的な行動を定義しています.
Promiseは対象であり、通常は将来完成する可能性のある非同期操作を表しています.この操作は成功するか失敗するかもしれないので、一つのPromiseオブジェクトは普通3つの状態があります.Pending、Fulfilled、Rejectied.それぞれ未完成、成功、操作失敗を表します.Promiseオブジェクトの状態がPendingからFulfilledまたはRejectiedのいずれかになると、その状態はもう変えられない.
Promiseオブジェクトには通常、then方法があります.この方法は、将来成功したら戻ってくるかもしれない値や失敗の原因を操作します.このthen方法はこうです.
promise.thenは通常2つの関数であり、一つは動作が成功した後の結果を処理するためのものであり、もう一つは操作が失敗した後の原因を処理するためのものであり、この2つの関数の1番目のパラメータはそれぞれ成功後の結果と失敗の原因であることが明らかになっている.then法に伝えられたのが関数ではない場合、このパラメータは無視されます.
then法の戻り値はPromiseオブジェクトであり、この特徴により、コントロールフローの効果を達成するためにチェーン式でthenを呼び出すことができます.ここには多くの詳細な問題があります.例えば、値の伝達やエラー処理などです.Promiseの仕様はこう定義されています.
OFulfilledまたはone Rejectied関数の戻り値がPromiseオブジェクトではない場合、この値は次のthenメソッドのOnFulfilledの最初のパラメータとして使用されます.戻り値がPromiseオブジェクトであれば、どのようにthenメソッドの戻り値がこのPromiseオブジェクトone Fulfilledまたはone Reject関数の中に異常がある場合、このthen方法の返却されたPromiseオブジェクト状態はRejectidに変わり、このPromiseオブジェクトがthenを呼び出すと、Errorオブジェクトはオン・レジェクトッド関数の最初のパラメータとしてPromise状態がFulfilledに変化し、thenメソッドにオンフル・フィルド関数が提供されない場合、thenメソッドが戻ってきたPromiseオブジェクト状態はFulfilledになり、Promiseオブジェクト状態がFule filledになり、Promise Promiseオブジェクト状態はPromise Promise Promise Promise PromiseRejected同理補足して、onFulfilledとonRejecedはすべて非同期で実行したのです.
規範の実現:q
上記はPromiseの仕様ですが、私達が必要なのはその実現です.qはPromise/A+に対してより良い実現規範を持っている倉庫です.
まずPromiseオブジェクトを作成し、Promiseオブジェクトの作成に関する仕様はPromise/Bにあります.ここでは詳細な説明はしないで、直接コードを付けます.
 
  
    function(flag){
        var defer = q.defer();
        fs.readFile("a.txt", function(err, data){
        if(err) defer.reject(err);
            else defer.resolve(data);
            });
            return defer.promise;
    }
多くのPromiseの実現はPromiseの作成に大同小異で、promise属性を持つdeferオブジェクトを作成することによって、値を取得したらdefer.reolveを呼び出し、失敗したらdefer.rejectを呼び出し、最後にdeferのpromise属性に戻ればいいです.このプロセスは、defer.reolveを呼び出してPromiseの状態をFulfilledにし、defer.rejectを呼び出してPromiseの状態をRejectにすると理解できる.
一連の非同期方法に対して、Promiseを使って綺麗なコードを書くにはどうすればいいですか?次の例を見てください.
 
  
    promise0.then(function(result){
        // dosomething
        return result;
    }).then(function(result) {
        // dosomething
        return promise1;   
    }).then(function(result) {
        // dosomething
    }).catch(function(ex) {
        console.log(ex);
    }).finally(function(){
        console.log("final");
    });
上のコードの中で、thenメソッドはOnFull filledだけを受け入れて、catch方法は実際にはthen(null、OnRejeced)です.これで一連の非同期方法が常に成功して値を返したら、コードは滝式の下方に運行します.もしその中の任意のステップが失敗したり、異常が発生したら、CommunJsのPromise規格によって、catchのfunctionを実行します.q finally方法も提供されていますが、字面的にもよく分かります.つまり、reolveであれrejectであれ、最終的にはfinallyのfunctionが実行されます.
よさそうですね.コードはもっとメンテナンスしてきれいになりました.もし合併したいなら?
 
  
     q.all([promise0, promise1, promise2]).spread(function(val0, val1, val2){
                    console.log(arguments);
                }).then(function(){
                    console.log("done");
                }).catch(function(err){
                    console.log(err);
                });
qも同時にapiを提供し、all方法を呼び出し、Promise配列を伝達すれば、thenのチェーンスタイルを引き続き使用することができる.また、q.nfbindなどNode.jsの生APIをPromiseに変換してコードフォーマットを統一するのもいいです.もっと多くのアプリはここでいちいち詳しく説明しません.
結論
本論文ではPromiseを使用してNode.js制御ストリーム問題を解決することを紹介しますが、Promiseは先端部にも同様に適用できます.EMCAScript 6はすでに生のAPIサポートを提供しています.Promiseは唯一の解決策ではないと指摘されています.asyncも良い選択で、より友好的な同時制御APIを提供します.
はい、まずここに来ます.皆さんのために役に立つと思います.