JS非同期モードとPromiseモード


JS非同期モードとPromiseモード-回転
リード
非同期モードはウェブプログラミングにおいてますます重要になり、ウェブの主流言語であるJavascriptにとって、このモードの実現は容易ではないため、多くのJavascriptライブラリ(例えばjQueryやDojo)はpromiseと呼ばれる抽象(deferredとも呼ばれることもある)を追加した.これらのライブラリにより,開発者は実際のプログラミングでpromiseモードを使用することができる.
IE公式ブログでは、XMLHttpRequest 2を使用してpromiseモードを実践する方法について詳しく説明した記事が発表されています.
関連する概念と応用を理解してみましょう.
XMLHttpRequestのオリジナルjs実装機能を採用
このような例を考慮すると、あるウェブページには非同期動作(XMLHttpRequest 2またはWeb Workersを介して)が存在する.Web 2.0技術の深化に伴い、ブラウザ側はますます多くの計算圧力に耐えているため、「同時」は積極的な意義を持っている.開発者にとって,ページとユーザのインタラクションを影響を受けないようにするとともに,ページと非同期タスクの関係を調整するという非線形実行のプログラミング要件に適応する困難がある.ページインタラクションはともかく,非同期呼び出しには成功操作と失敗処理の2つの結果を処理する必要があることが考えられる.呼び出しに成功すると、返された結果を別のAjaxリクエストに使用する必要がある場合があります(筆者の別の記事「NodeJSの非同期プログラミングスタイル」では詳細な説明があります).これはPyramid of Doomと呼ばれるプログラミングの複雑さをもたらす.次の例を見てみると、重なり合っていて、とてもイメージ的です.
(function($) { $(function(){ $("#btnUpdateDataAndUI").click(function(e) { $.get("http://myawesomeapi.io/api/people", function(data) { $(".rows").each(function() { var that = this; $(this).hover(function(e) { setTimeout(function() { $(that).fadeOut(); }, 1000); }); }); }); }); }); })(jQuery);

次のコード例を見てみましょう(XMLHttpRequest 2ベース):
function searchTwitter(term, onload, onerror) {

     var xhr, results, url;
     url = 'http://search.twitter.com/search.json?rpp=100&q=' + term;
     xhr = new XMLHttpRequest();
     xhr.open('GET', url, true);

     xhr.onload = function (e) {
         if (this.status === 200) {
             results = JSON.parse(this.responseText);
             onload(results);
         }
     };

     xhr.onerror = function (e) {
         onerror(e);
     };

     xhr.send();
 }

 function handleError(error) {
     /* handle the error */
 }

 function concatResults() {
     /* order tweets by date */
 }

 function loadTweets() {
     var container = document.getElementById('container');

     searchTwitter('#IE10', function (data1) {
         searchTwitter('#IE9', function (data2) {
             /* Reshuffle due to date */
             var totalResults = concatResults(data1.results, data2.results);
             totalResults.forEach(function (tweet) {
                 var el = document.createElement('li');
                 el.innerText = tweet.text;
                 container.appendChild(el);
             });
         }, handleError);
     }, handleError);
 }

上のloadTweetsコードは、TwitterのhashtagがIE 10とIE 9の内容を取得し、ページに表示する機能です.このネストされたコールバック関数は理解しにくいが,開発者はどのコードが応用に用いられるビジネスロジックを注意深く分析し,どのコードが非同期関数呼び出しを処理し,コード構造が支離滅裂であるかを分析する必要がある.エラー処理も分解され,各箇所でエラーの発生を検出し,対応する処理を行う必要がある.
非同期プログラミングの複雑さを低減するために,開発者は非同期操作を処理するための簡便な方法を探してきた.1つの処理モードはpromiseと呼ばれ、長時間稼働する可能性があり、必ずしも完全な操作を必要としない結果を表す.このモードは、長時間の操作の完了をブロックしたり待つのではなく、承諾された結果を表すオブジェクトを返します.
このような例を考慮すると、ページコードはサードパーティのAPIにアクセスする必要があり、ネットワーク遅延により応答時間が長くなる可能性があります.この場合、非同期プログラミングを採用することは、ページ全体とユーザーのインタラクションに影響を与えません.promiseモードは、通常、状態変化時に対応するコールバック関数を登録するためのthenと呼ばれる方法を実現する.
例えば、次のコード例:searchTwitter(term).then(filterResults).then(displayResults);promiseモード状態
promiseモードは、未完了(unfulfilled)、完了(resolved)、拒否(rejected)の3つの状態のいずれかです.CommonJS Promise/A規格を例にとると、promiseオブジェクト上のthenメソッドは、完了および拒否された状態での処理関数の追加を担当します.thenメソッドは、promiseパイプを形成するために別のpromiseオブジェクトを返します.このpromiseオブジェクトを返す方法は、then(resolvedHandler、rejectedHandler)のような非同期操作を開発者が直列に接続することをサポートします.resolvedHandlerコールバック関数はpromiseオブジェクトが完了状態に入るとトリガーされ、結果が伝達されます.rejectedHandler関数は拒否状態で呼び出されます.
promiseモード実現機能を採用
promiseモードがあれば、上記のTwitterの例を再実現することができます.実装方法をよりよく理解するために,ゼロからpromiseモードのフレームワークを構築することを試みた.まずpromiseを格納するオブジェクトが必要です.
var Promise = function () {
        /* initialize promise */
};

次にthenメソッドを定義し、完了ステータスと拒否ステータスを処理するために2つのパラメータを受け入れます.
Promise.prototype.then = function (onResolved, onRejected) {
     /* invoke handlers based upon state transition */
 };

同時に、理が完了していないresolve関数と拒否された状態遷移reject関数を実行する2つの方法も必要である.
Promise.prototype.resolve = function (value) {
     /* move from unfulfilled to resolved */
 };

 Promise.prototype.reject = function (error) {
     /* move from unfulfilled to rejected */
 };

ここでpromiseの棚を構築し、IE 10のコンテンツのみを取得すると仮定して、上記の例を続行することができます.Ajaxリクエストを送信してpromiseにカプセル化する方法を作成します.このpromiseオブジェクトはそれぞれxhr.onloadとxhr.onerrorでは完了と拒否状態の遷移プロセスが指定されています.searchTwitter関数がpromiseオブジェクトを返すことに注意してください.次にloadTweetsでthenメソッドを使用して完了状態と拒否状態に対応するコールバック関数を設定します.
function searchTwitter(term) {
    var url, xhr, results, promise;
    url = 'http://search.twitter.com/search.json?rpp=100&q=' + term;
    promise = new Promise(); //    promise  
    xhr = new XMLHttpRequest();
    xhr.open('GET', url, true);

    xhr.onload = function (e) {
        if (this.status === 200) {
            results = JSON.parse(this.responseText);
            promise.resolve(results);
        }
    };

    xhr.onerror = function (e) {
        promise.reject(e);
    };

    xhr.send();
    //   promise  
    return promise;    
}

function loadTweets() {
    var container = document.getElementById('container');
    //    promise   then        
    searchTwitter('#IE10').then(function (data) {
        data.results.forEach(function (tweet) {
            var el = document.createElement('li');
            el.innerText = tweet.text;
            container.appendChild(el);
        });
    }, handleError);
}

これまで,promiseモードを単一のAjaxリクエストに適用することができたが,promiseの優位性はまだ現れていないようだ.複数のAjaxリクエストの同時コラボレーションを見てみましょう.この場合、呼び出しの準備をするpromiseオブジェクトを格納する別の方法whenが必要です.あるpromiseが完了していない状態を完了または拒否状態に変換すると、thenメソッドの対応する処理関数が呼び出されます.whenメソッドは、すべての操作が完了するのを待つ必要がある場合に重要です.
Promise.when = function () {
    /* handle promises arguments and queue each */
};

先ほどIE 10とIE 9の2つのコンテンツを取得したシーンを例にとると、コードを書くことができます.
var container, promise1, promise2;
container = document.getElementById('container');
promise1 = searchTwitter('#IE10');
promise2 = searchTwitter('#IE9');

//      promise      
Promise.when(promise1, promise2).then(function (data1, data2) {

    /* Reshuffle due to date */
    var totalResults = concatResults(data1.results, data2.results);
    totalResults.forEach(function (tweet) {
        var el = document.createElement('li');
        el.innerText = tweet.text;
        container.appendChild(el);
    });
}, handleError);

上のコードを解析すると,when関数は2つのpromiseオブジェクトの状態が変化するのを待ってから具体的な処理を行うことが分かった.実際のPromiseライブラリでは、when関数にはwhenなど多くの変種があります.some()、when.all()、when.any()など、読者は関数の名前からいくつかの意味を推測することができ、詳細な説明はCommonJSのpromiseを参考にしてwhenを実現することができる.js.
CommonJSを除いて、他の主流のJavascriptフレームワーク、例えばjQuery、Dojoなどはすべて自分のpromise実現が存在します.開発者はこのモードをよく利用して非同期プログラミングの複雑さを低減しなければならない.
Dojoを例に挙げて、その実現に何か異同があるか見てみましょう.
Dojoフレームワークでpromiseモードを実現するオブジェクトはDeferredであり、then関数もあり、完了と拒否状態を処理し、直列をサポートすると同時に、resolveとrejectもあり、前述したように機能している.次のコードはTwitterのシーンを完成させました.
function searchTwitter(term) {

    var url, xhr, results, def;
    url = 'http://search.twitter.com/search.json?rpp=100&q=' + term;
    def = new dojo.Deferred();
    xhr = new XMLHttpRequest();
    xhr.open('GET', url, true);

    xhr.onload = function (e) {
        if (this.status === 200) {
            results = JSON.parse(this.responseText);
            def.resolve(results);
        }
    };

    xhr.onerror = function (e) {
        def.reject(e);
    };

    xhr.send();
    return def;
}

dojo.ready(function () {
    var container = dojo.byId('container');
    searchTwitter('#IE10').then(function (data) {
        data.results.forEach(function (tweet) {
            dojo.create('li', {
                innerHTML: tweet.text
            }, container);
        });
    });
});

それだけじゃなくdojoに似ていますxhrGetメソッドはdojoを返す.Deferredオブジェクトなので、promiseモードを自分で包装する必要はありません.
var deferred = dojo.xhrGet({
    url: "search.json",
    handleAs: "json"
});

deferred.then(function (data) {
    /* handle results */
}, function (error) {
    /* handle error */
});

それ以外にDojoはdojoを導入した.DeferredListは、開発者が複数のdojoを同時に処理することをサポートする.Deferredオブジェクト、これは実は上述したwhen方法のもう一つの表現形式である.
dojo.require("dojo.DeferredList");
dojo.ready(function () {
    var container, def1, def2, defs;
    container = dojo.byId('container');
    def1 = searchTwitter('#IE10');
    def2 = searchTwitter('#IE9');

    defs = new dojo.DeferredList([def1, def2]);

    defs.then(function (data) {
        // Handle exceptions
        if (!results[0][0] || !results[1][0]) {
            dojo.create("li", {
                innerHTML: 'an error occurred'
            }, container);
            return;
        }
        var totalResults = concatResults(data[0][1].results, data[1][1].results);

        totalResults.forEach(function (tweet) {
            dojo.create("li", {
                innerHTML: tweet.text
            }, container);
        });
    });
});

ここで、読者はすでにpromiseモードに対して1つの比較的に完全な理解があって、非同期のプログラミングはますます重要になって、このような情況の下で、私達は方法を見つけて複雑さを下げる必要があって、promiseモードは1つのとても良い例で、その風格は比較的に人間化して、その上主流のJSフレームワークは自分の実現を提供しました.プログラミングの実践の中で、開発者はこのような便利なプログラミング技術を試しなければならない.なお、promiseモードの使用には、promiseオブジェクトを適切に設定し、対応するイベントで状態変換関数を呼び出し、最後にpromiseオブジェクトを返す必要がある.
フロントエンドのJSライブラリだけでなく、現在ホットなNodeJSプラットフォームにも多くのサードパーティ製promiseモジュールが登場しており、具体的なリストはここasync-flowにアクセスできます.