スタッフブログ Staff Blog

複数の処理結果を待って任意の処理を実行する jQuery.when() メソッド

Movable TypeData API を利用する場合などに、2つ以上の処理結果を基に任意の処理を実行したい場合が出てきます。例えば、「詳説 Data API 2.0 Vol.3: さまざまな条件で記事を取得する | MovableType.jp - CMSプラットフォーム Movable Type -」の中で紹介されている、「指定したアイテムと関連付けられている記事を一覧する」というパターンです。

サンプルコードでは、次のように3つの処理が同期的に処理されるようにコーディングされています。

  1. アイテムの一覧を取得する
  2. アイテムに紐付くエントリーを取得する
  3. アイテムのサムネイルを取得する

処理内容を検討すると、 2. と 3. はどちらも asset_id を元に処理を行っているので、同時に API へアクセスしても問題なさそうです。このような場合に jQuery.when() を使うと、コードを簡潔にまとめることができます。

jQuery.when() は、1つ以上の Deferred オブジェクトを受け取り、 Promise を返します。渡された Deferred オブジェクトが全て resolve すると、 jQuery.when() で作成されたマスター Deferred オブジェクトも resolve し、 doneCallbacks が実行される仕組みです。

今回の例の場合、エントリーの取得処理とサムネイルの取得処理を実行しそれぞれの Deferred オブジェクト(厳密には jqXHR オブジェクト)を受け取ることができるので、それらを $.when() に渡します。 Ajax を用いた API からのデータ受信が全て完了すると HTML 生成コードが実行されるように .then() で指定します。

以下にリファクタリングしたコードを示します。

(function ($) {
    var apiUrl = "http://path/to/mt/mt-data-api.cgi/v2";

    $.ajax({
        url: apiUrl + "/sites/[blog_id]/assets",
        type: "GET",
        dataType: 'json',
    })
    .done(function (assets) {
        var nAsset = assets.totalResults;
        var html = "";

        if(nAsset < 1) {
            return;
        }

        // アセット毎に処理を実行
        assets.items.forEach(function (item) {
            var asset_id = item.id;
            var getEntriesUrl = apiUrl + "/sites/[blog_id]/assets/" + asset_id + "/entries?limit=1";
            var getThumbUrl = apiUrl + "/sites/[blog_id]/assets/" + asset_id + "/thumbnail?width=640";
            var getEntriesDfd;
            var getThumbDfd;

            // エントリーの取得
            getEntriesDfd = $.ajax({
                url: getEntriesUrl,
                type: "GET",
                dataType: 'json'
            });

            // サムネイルの取得
            getThumbDfd = $.ajax({
                url: getThumbUrl,
                type: "GET",
                dataType: 'json'
            });

            // エントリーの取得とサムネイルの取得が完了したら、HTMLを生成する
            $.when(getEntriesDfd, getThumbDfd).then(function (entriesResult, thumbResult) {
                if (entriesResult[0].totalResults > 0) {
                    html += '<a href="'+ entriesResult[0].items[0].permalink + 
                            '"><img src="' + thumbResult[0].url + 
                            '" width="200" alt="' + entriesResult[0].items[0].title + '"></a>';
                } else {
                    html += '<img src="' + thumbResult[0].url + '" width="200">';
                }

                nAsset -= 1;

                if (nAsset === 0) {
                    // 全てのアイテムに対して処理が完了したら生成したHTMLを表示する
                    $("#result").append(html);
                }
            });
        });
    });
}(jQuery));

このように、 jQuery.when() や Deferred を利用するとコードのネストを減らし、見通しを良くすることができます。また、複数の処理を非同期で実行できるので、全体の処理完了までの時間がいくらか短くなることも考えられます。

2015年6月17日に採択が発表された ECMAScript 6 でも Promise によって非同期処理のインターフェースが標準化されました。 jQuery の Deferred や、 ES6 の Promise を利用して、よりよいコードを目指していきたいと考えています。

余談ですが、私は JavaScript などを書く際、横方向の見やすさを考慮し、1行のコードを半角80字前後に収める(もしくは改行する)ように努めています。

お問い合わせ Contact

制作のご依頼、ご相談などは下記のフォームからご連絡ください。