目次


ヒント

非同期コールバックを最大限に利用する

JavaScript アプリケーションでデータ・ソースを適宜利用するための調整

Comments

コンテンツシリーズ

このコンテンツは全#シリーズのパート#です: ヒント

このシリーズの続きに乞うご期待。

このコンテンツはシリーズの一部分です:ヒント

このシリーズの続きに乞うご期待。

はじめに

非同期データ・ソースの問題は、…同期がとれていないことです。特に、HTTP プロトコルのリクエストによって送信されるデータは期待していたタイミングよりもずっと遅く到着するかもしれず、あるいはリクエストはタイムアウトしてしまうか、そうでなければ完全な失敗となってしまうかもしれません。TCP 層のプロトコルはどれも信頼性がないことは周知のとおりですが、その影響で必要なデータがそろわないと、Ajax アプリケーションの場合は特に、複数のサーバー間に依存関係を持つデータが存在する可能性があるため、Web アプリケーション全体の動作に影響を及ぼすことになります。

依存関係を持つデータの処理は、必ずしも Ajax アプリケーションに特有なものではありません。あらゆる種類のアプリケーションが、セマフォーやキュー、共有変数などを使って、状態に関する通信をプロセス間で行います。この記事で取り上げるプロセスは、データを取得するための一般的なリクエストです。しかし Web アプリケーションでは、他の種類のアプリケーションに比べて (特に、厳密にローカルで実行されるアプリケーションと比較した場合には)、タイムアウトや、サーバーやネットワークに関する他の問題が起こりやすいものです。しかも、さまざまなデータ・ソースでの (さらには同じデータ・ソースに対する複数のリクエストでの) タイミングのばらつきは、Web ベースのアプリケーションの方が、他の種類のアプリケーションよりも、さらにはデータベースなどのネットワーク・ベースのリソースを利用する大部分のアプリケーションよりも、大きいものです。

リクエストの状態とタイムアウト

皆さんが遭遇する、XMLHttpRequest() を使った非同期 Ajax ポーリングの大部分のコード例は、200 OK というステータス・コードを受け取るかどうかのチェックしか行いません。しかしこのチェックの機能を強化し、他に考えられるいくつかの状態に対するチェックや、タイムアウトそのものを表す「疑似的な状態」のチェックも含めるようにお勧めします。最近私が developerWorks に寄稿したヒント「セッション状態を使って不必要な Ajax トラフィックを回避する」(「参考文献」にリンクがあります) の中で、304 ステータス・コード を利用する例を説明しましたが、その方法は適切な方法として変わらず一般に通用します。

さまざまな HTTP ステータス・コードを適切に処理するのは良い考えです。しかし、200 OK 以外の 2xx コードをどう扱う必要があるかは、あまりはっきりしていません。例えばアプリケーションがサーバー・リソースを作成する場合であれば、201 Created コードをチェックし、レスポンスの中の URI を調べるとよいかもしれません。また、ステータス・コード 301、302、303、307 は、クライアントが透過的にリダイレクトしなければならないものであり、最終的に 200 ステータス (あるいはタイムアウト、あるいは 4xx ステータスか 5xx ステータス) が得られるため、実際には気にする必要はありません。4xx エラーに関しては、5xx エラーとは異なる処理をした方が賢明でしょう。非常に大まかに言えば、おそらく 4xx ステータス・コードは (特に、一般的な 404 Not Found は)、クライアント・アプリケーションが指定した URL に問題があることを意味しています。5xx エラーはサーバーの問題であり、少しの間待ってから再試行すれば、おそらく解決されてしまいます。

さまざまな種類のサーバー・エラーを処理することは重要ですが、それと同じくらい重要なことは、サーバーがハングアップしたままで、まったく何もレスポンスを返さない場合の処理です。ハングアップは 5xx コードと「事実上等価」ですが、簡単に XMLHttpRequest().status コードをチェックして状態を確認することはできない点が異なります。このような場合、setTimeout() タイマーを使用して、タイムアウトしたリクエストをキャンセルすることで対処できます。

このステータス・コードに関するチェックをひとまとめにすると、Ajax のデータ・ソースをリクエストするコードはリスト 1 のようなものになります。

リスト 1. データ・ソースへのリクエストを確実に処理する
function getResource(uri, data_callback, error_callback, timeout) {
    var tryAgain = function () {
      getResource(uri, data_callback, error_callback, timeout);
    }
    var r = new XMLHttpRequest();
    var timer = setTimeout(
        function() {
            r.abort();
            r.onreadystatechange = null;
            setTimeout(tryAgain, timeout);
        },
        timeout);
    r.open("GET", uri, true);
    r.onreadystatechange = function() {
        if (r.readyState != 4) {
            // Ignore non-loaded readyStates
            // ...will timeout if do not get to "Loaded"
            return;
        }
        clearTimeout(timer);  // readyState==4, no more timer
        if (r.status==200) {  // "OK status"
              data_callback(r.responseText);
        }
        else if (r.status==304) {
            // "Not Modified": No change to display
        }
        else if (r.status >= 400 && r.status < 500) {
            // Client error, probably bad URI
            error_callback(r)
        }
        else if (r.status >= 500 && r.status < 600) {
            // Server error, try again after delay
            setTimeout(tryAgain, timeout);
        }
        else {
            error_callback(r);
        }
    }
    r.send(null);
    return r;
}

もちろん、getResource() に対してさまざまな機能強化をすることができます。例えば、タイムアウト状態、あるいはサーバー・エラー状態が発生した場合には、一定時間後に単に tryAgain() を呼び出します。あるいは tryAgain() を呼びす代わりに、この 2 つの状態のいずれかに対して、別のコールバックを使用することもできます。すぐにリクエストに失敗することが想定され、成功するまで繰り返しリクエストを行うことを要求しない場合には、渡された関数 error_callback() が使われます。

データを利用できるようにするための調整

ここで示した getResource() 関数は、データ・リソースを取得してそれを data_callback() に提供するために、永遠に待ち続けるかもしれません。しかし、このデータ・コールバック自体が、他のデータが得られるかどうかに依存しているかもしれません。複数のデータ・ソースをうまく扱うための最も簡単な方法は、単にデータ・ソースの内容をグローバル変数に配置して、データが「利用される」時にそれらの変数を空にするという方法です。例えば、基本的なコールバックはリスト 2 のようなものになります。

リスト 2. データ・ソースが 2 つある場合の基本的なコールバック
var other_data = null;
function processOtherData(responseText) {
    other_data = responseText;
}
function processData(this_data) {
    var delay = 1000;     // Keep trying at 1-second intervals
    if (other_data == null) {
        setTimeout(function() { processData(this_data); }, delay);
        return;
    }
    // We have both this_data and other_data
    displayThisAndThat(this_data, other_data);
    // Reset other_data now that we have consumed it
    other_data = null;
}

アプリケーションが getResource(uri1,processOtherData,...) を呼び出したら、別の場所で getResource(uri2,processData,...) を呼び出すようにします。後者の呼び出しによって起動されるコールバックは、実際にデータが追加されるまで内部的に other_data を「ポーリング」し、データが処理されたら other_data をヌルにします。最初の呼び出しが成功するまで後者の呼び出しの繰り返しを避けるための、そして/あるいは、後から processData() が呼び出されたら新しい呼び出しを優先して clearTimeout() を呼び出すための、もう 1 つのセマフォーが必要かもしれません。

まとめ

データを適宜利用できるようにするためのさまざまな調整は、Ajax アプリケーションに限らず、どのような種類のアプリケーションでもその時々で複雑になりがちです。ここで示した例は、中心となる 1 つのデータ表示関数 processData() と、さらなるデータを取得する補助的なコールバック関数 processOtherData() を使っているだけですが、このパターンは非常に容易に一般化することができ、相互に依存する幅広い種類のデータ・ソースに対しても適用することができます。

またどのような場合でも、関数 getResource() は、HTTP を介して「一生懸命努力して」データを取得するための汎用フレームワークとして適切です。通常は、クライアント・サイドでリクエストを作成する際にエラーがない限り、データの取得には最終的に成功します。つまり一時的に受け取るネットワーク・エラーやサーバー・エラーは単なる遅延を意味するにすぎず、失敗を意味するわけではありません。もちろん、Ajax アプリケーションではいつも必要なことですが、複数のドメインからデータを取得するためには何らかの特別な手法を使う必要があります。一般的には XMLHttpRequest() オブジェクトを別の隠し IFrames に含めることで別のドメインをポーリングしますが、この手法は非同期のデータを扱うための調整だけに必要な特別なものではありません。


ダウンロード可能なリソース


関連トピック


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Web development, Java technology, XML
ArticleID=282904
ArticleTitle=ヒント: 非同期コールバックを最大限に利用する
publish-date=12112007