Ajax をマスターする: 第 2 回 JavaScript と Ajax を使用して行う非同期要求

Web 要求に XMLHttpRequest を使用する

大抵の Web アプリケーションでは、サーバーから HTML ページをまるごと取得する要求/応答モデルを使用します。このモデルを使用したやり取りは、ボタンをクリックしてサーバーからの応答を待ち、また別のボタンをクリックして再び待機するといったものになってしまいます。一方 Ajax と XMLHttpRequest オブジェクトを使用すれば、ユーザーがサーバーからの応答を待つ必要のない要求/応答モデルを使用することができます。今回の記事では、Brett McLaughlin が特定のブラウザーに依存しないように XMLHttpRequest インスタンスを作成する方法、要求を作成して送信する方法、そしてサーバーからの応答を処理する方法を説明します。

Brett McLaughlin, Author and Editor, O'Reilly Media Inc.

Photo of Brett McLaughlinBrett McLaughlin は、Logo の時代からコンピューター業界に携わっています (あの小さな三角形を覚えていますか)。近年、Java および XML コミュニティーでもっとも著名な作成者兼プログラマーの 1 人です。彼の功績には Nextel Communications での複合エンタープライズ・システムのインプリメンテーション、Lutris Technologies でのアプリケーション・サーバーの作成があり、最近では O'Reilly Media, Inc. で関連本の著作および編集に従事しています。近日出版予定の著書『Head Rush Ajax ― 学びながら読む Ajax 入門』では、賞を獲得した革新的な Head First アプローチを Ajax にもたらしています。最近の著作には、最新バージョンの Java テクノロジーに関する最初の本、『Java 5.0 Tiger』があります。また、彼の著名な『Java & XML』は今でも、Java 言語での XML テクノロジーの使用方法に関するもっとも信頼のおける著作物の 1 つとして数えられています。



2006年 1月 17日

この連載の前回の記事 (「参考文献」にリンクを記載) では、Ajax アプリケーションを紹介し、Ajax アプリケーションを動かす中心となる基本概念をいくつか取り上げて検討しました。Ajax の中心にあるのは、JavaScript、HTML と XHTML、多少の動的 HTML、そしていくらかの DOM (Document Object Model) といった、おそらく皆さんにはすでにお馴染みの多数の技術です。前回は Ajax の全体像を 10,000 フィートの上空から眺めましたが、今回の記事では Ajax の細部にズームインします。

この記事でまず取り上げるのは、すべての Ajax 関連のオブジェクトおよびプログラミング手法にとって最も重要な基本となる XMLHttpRequest オブジェクトです。このオブジェクトはまさに、すべての Ajax アプリケーションに唯一共通するスレッドであり、お察しのとおり、Ajax プログラミングで最大限の能力を発揮するには、このオブジェクトを完全に理解する必要があります。実のところ、XMLHttpRequest を適切に使用するために、XMLHttpRequest を明示的に使用しない場合もあります。一体これはどういうことなのでしょうか。

Web 2.0 の概略

コードの詳細に入る前に、概要としてはこれが最後となるこのセクションを読んで Web 2.0 の概念を完全に理解してください。Web 2.0 という言葉を聞くとまず、「それでは、Web 1.0 とは一体何なのか」という疑問を持つはずです。Web 1.0 について耳にすることはほとんどないと思いますが、Web 1.0 は、極めて明確な要求/応答モデルを使用する従来の Web を意味します。例えば、Amazon.com にアクセスしてボタンをクリックするか、検索語を入力してみてください。サーバーに対して要求が行われると、ブラウザーに応答が返ってきます。けれどもこの応答に含まれるのは、単なる本のタイトル・リストだけではありません。実は、別の完全な HTML ページです。そのため、この新しい HTML ページで Web ブラウザーの画面が再描画される際には、画面が点滅したり、ちらついたりすることになります。要するに、要求と応答は表示される新しいページごとに明確に見分けられるということです。

Web 2.0 は、このあからさまなやりとりを (かなりの程度まで) 省きます。その一例として、Google マップや Flickr のようなサイトにアクセスしてみてください (Ajax で強化されたこの 2 つの Web 2.0 サイトへのリンクについては、「参考文献」を参照してください)。例えば Google マップでは、マップをドラッグするにもズームイン/ズームアウトするにも、ほとんど再描画は行われません。もちろん、要求と応答は行われていますが、すべては裏で進行しています。ユーザーにとって、このエクスペリエンスは非常に快適なものであり、かなりデスクトップ・アプリケーションに似たような感覚を味わえます。この新しい感覚とパラダイムこそが、Web 2.0 という言葉が表している内容です。

そこで考えなればならないのは、これらの新しいやりとりを可能にする方法です。もちろん要求とフィールドの応答を行わなければならないことに変わりはありません。しかし、遅くて魅力のない Web インターフェースという感覚を与える原因は、要求/応答のやりとりの度に行われる HTML の再描画です。このことから、要求に対して HTML ページ全体ではなく、必要なデータだけが含まれた応答を受け取れるようにする方法が必要であることは明らかです。新しい HTML ページ全体を取得する必要があるのは、ユーザーに新しいページを表示したい場合のみに限られます。

しかしほとんどのやりとりでは、詳細が追加されたり、既存のページで本文のテキストやオーバーレイ・データが変更されたりするといったものです。このような場合すべてにおいて、Ajax と Web 2.0 の手法では HTML ページ全体を更新することなく、データの送受信をすることができます。Web アプリケーションでこうしたことが可能になると、Web サーフィンの常連にとって、そのアプリケーションは動きが速く、応答性に優れているという印象を持つようになるため、彼らが何度もサイトに戻ってくる結果となるはずです。


XMLHttpRequest について

この速さと驚異を実現するには、XMLHttpRequest という JavaScript オブジェクトについてとことん理解する必要があります。この小さなオブジェクト (実際にはかなり長い間、いくつかのブラウザーで使用されてきました) が、Web 2.0、Ajax、そして今後数ヶ月間にわたってこの連載で学ぶほとんどの内容にとって重要な鍵となります。ごく簡単に概要を説明すると、このオブジェクトで使用するメソッドとプロパティーは以下のようにほんのわずかしかありません。

  • open(): サーバーに対する新規要求をセットアップします。
  • send(): 要求をサーバーに送信します。
  • abort(): 現行の要求を中止します。
  • readyState: 現行の HTTP Ready 状態を示します。
  • responseText: 要求に応じてサーバーが返すテキストです。

このすべてを (あるいは、いずれかを) 理解していなくても、ご心配なく。それぞれのメソッドとプロパティーについては、今後の数回の記事で説明していきます。それでも上記から、XMLHttpRequest を使って行う内容については十分に理解できるはずです。これらのメソッドとプロパティーのそれぞれが、要求の送信と応答の処理に関連しています。実際、XMLHttpRequest のすべてのメソッドとプロパティーを見れば、いずれもあの極めてシンプルな要求/応答モデルに関連していることがわかります。つまり明らかなことは、これからの手順では、驚くべき新たな GUI オブジェクトや、ユーザー・インターフェースを作成するための何らかの極意を学ぶのではなく、シンプルな要求とシンプルな応答を扱うということです。こう言うと興味深いものには聞こえないかもしれませんが、この 1 つのオブジェクトを慎重に使用することによって、アプリケーションをすっかり変身させることができます。

新規作成の単純さ

まず必要なのは、変数を新規に作成して XMLHttpRequest オブジェクトのインスタンスに割り当てる作業です。これは JavaScript ではごく簡単なことで、リスト 1 のように、new というキーワードとオブジェクト名を使用すればよいだけのことです。

リスト 1. 新規 XMLHttpRequest オブジェクトを作成する
<script language="javascript" type="text/javascript">
var request = new XMLHttpRequest();
</script>

ご覧のように、難しい作業ではありません。JavaScript では、変数に型を定義する必要はないことを思い出してください。したがって、リスト 2 のようなコードはまったく必要ありません (このオブジェクトを Java で作成するとしたら、このようなコードになります)。

リスト 2. Java で XMLHttpRequest を作成する場合のコード
XMLHttpRequest request = new XMLHttpRequest();

JavaScript では var を使って変数を作成し、その変数に名前 (この例では「request」) を付けてから、XMLHttpRequest の新規インスタンスに割り当てます。これで、このオブジェクトを関数で使用する準備は完了です。

エラー処理

現実の世界では何事も失敗する可能性がありますが、このコードにはエラー処理がありません。エラー処理を行う比較的適切な方法は、XMLHttpRequest オブジェクトを作成し、何かが上手くいかなくなった場合にはグレースフルに失敗させるというものです。例えば多くの古いブラウザー (驚くべきことに、古いバージョンの Netscape Navigator を使用しているユーザーはまだいます) では XMLHttpRequest をサポートしていないため、これらのブラウザーのユーザーには、エラーが発生したときに何らかの問題があることを通知しなければなりません。リスト 3 に一例として、XMLHttpRequest オブジェクトを作成しようとしてエラーが発生した際に JavaScript アラートをスローさせる方法を示します。

リスト 3. エラー処理機能を備えた XMLHttpRequest を作成する
<script language="javascript" type="text/javascript">
var request = false;
try {
  request = new XMLHttpRequest();
} catch (failed) {
  request = false;
}

if (!request)
  alert("Error initializing XMLHttpRequest!");
</script>

上記のステップの 1 つひとつを確実に理解してください。

  1. request という名前の変数を新規に作成し、この変数に false の値を割り当てます。false は、XMLHttpRequest オブジェクトがまだ作成されていないことを表す値として使用します。
  2. try/catch ブロックを追加します。
  3. XMLHttpRequest オブジェクトの作成を試行します。
  4. この試行が失敗した場合 (catch (failed) 節が実行される場合)、request を引き続き false に設定します。
  5. request が false のままかどうかを調べます (問題がなければ、false にはなっていないはずです)。
  6. 問題がある場合 (そして request が false の場合)、JavaScript アラートを使用して、ユーザーに問題が発生したことを通知します。

このようにかなりシンプルなコードです。大抵の JavaScript 開発者および Web 開発者にとっては、このコードを読んでその内容を書くほうが、その内容を理解するよりも時間がかかるほどです。これで、XMLHttpRequest オブジェクトを作成して、エラーが発生した場合には通知までしてくれるエラー対策用のコードが用意できました。

Microsoft への対処

万事順調なように思えますが、それはこのコードを Internet Explorer で試してみるまでの話です。このコードを Internet Explorer で実行すると、図 1 のような極めて不愉快なエラーが表示されます。

図 1. エラーをレポートする Internet Explorer
エラーをレポートする Internet Explorer

Microsoft と上手くつきあうこと

Ajax と、この分野への Microsoft の高まる関心とその存在については数多くの記事が書かれていますが、事実、Microsoft Internet Explorer の最新バージョン (2006年後半に登場予定のバージョン 7) では XMLHttpRequest を直接サポートする予定です。この移行により、すべての Msxml2.XMLHTTP 作成コードに代わって new キーワードを使えるようになります。しかし期待しすぎないでください。古いブラウザーをサポートしなければならないことに変わりはないので、どのブラウザーにも対応したコードがすぐに不要になるというわけではありません。

何らかの問題があることは確かですが、Internet Explorer は時代遅れのブラウザーとは言い難く、世界の約 70 パーセントが Internet Explorer を使用しています。つまり、Microsoft と Internet Explorer をサポートしなければ、Web の世界では上手くやっていけないということです。そのため、Microsoft 製のブラウザーに対処する方法が別途必要となります。

Microsoft は Ajax をサポートするものの、Microsoft では XMLHttpRequest を別の名前で呼んでいることが、このエラーの理由です。実のところ、その名前は 1 つだけではありません。Internet Explorer の最近のバージョンを使用している場合は、Msxml2.XMLHTTP という名前のオブジェクトを使用する必要があります。一方、古いバージョンの Internet Explorer ではこのオブジェクトを Microsoft.XMLHTTP という名前で使用しているため、この 2 つのオブジェクト・タイプをサポートしなければなりません (しかも、Microsoft 製でないブラウザーに対するサポートを損なってもなりません)。そこでリスト 4 では、これまで見てきたコードに Microsoft 用のサポートを追加しています。

リスト 4. Microsoft 製ブラウザーのサポートを追加する
<script language="javascript" type="text/javascript">
var request = false;
try {
  request = new XMLHttpRequest();
} catch (trymicrosoft) {
  try {
    request = new ActiveXObject("Msxml2.XMLHTTP");
  } catch (othermicrosoft) {
    try {
      request = new ActiveXObject("Microsoft.XMLHTTP");
    } catch (failed) {
      request = false;
    }
  }
}

if (!request)
  alert("Error initializing XMLHttpRequest!");
</script>

中括弧のなかは、混乱しやすいので、以下に、段階を追って内容を説明します。

  1. request という名前の新規変数を作成し、この変数に false の値を割り当てます。false は、XMLHttpRequest オブジェクトがまだ作成されていないことを表す値として使用します。
  2. try/catch ブロックを追加します。
    1. XMLHttpRequest オブジェクトの作成を試行します。
    2. この試行が失敗した場合 (catch (trymicrosoft) 節が実行される場合) は以下の処理を行います。
      1. Microsoft の最近のバージョンのオブジェクト (Msxml2.XMLHTTP) を使用して、Microsoft 対応オブジェクトの作成を試行します。
      2. この試行が失敗した場合 (catch (othermicrosoft) 節が実行される場合)、Microsoft の古いバージョンのオブジェクト (Microsoft.XMLHTTP) を使用して Microsoft 対応オブジェクトの作成を試行します。
    3. それでも失敗した場合には (catch (failed) 節が実行される場合)、request を引き続き false に設定します。
  3. request が false のままかどうかを調べます (問題がなければ、false にはなっていないはずです)。
  4. 問題がある場合 (そして request が false の場合)、JavaScript アラートを使用して、問題があることをユーザーに通知します。

以上の変更をコードに加えてから Internet Explorer で再びコードを試してみてください。エラー・メッセージが表示される代わりに、作成したフォームが表示されるはずです。私の場合、図 2 のような結果となりました。

図 2. 正常に機能している Internet Explorer
正常に機能している Internet Explorer

静的か、それとも動的か

リスト 1リスト 3リスト 4 をもう一度見てみると、いずれのコードも script タグのなかに直接ネストされてことがわかります。JavaScript がこのようにコーディングされ、メソッドや関数の本体に組み込まれていない場合には、静的 JavaScript と呼ばれます。つまりこのコードは、ページがユーザーに表示される前の何らかの時点で実行されるということです (仕様からは、このコードが実行されてブラウザーの動作に影響を与える正確なタイミングは 100 パーセント明らかではありませんが、コードが実行されてからでないと、ユーザーがページと対話できないことは確かです)。ほとんどの Ajax プログラマーはこの方法で、XMLHttpRequest オブジェクトを作成しています。

とは言うものの、このコードをメソッド内に組み込むことも可能です (リスト 5 を参照)。

リスト 5. XMLHttpRequest 作成コードをメソッド内に組み込む
<script language="javascript" type="text/javascript">

var request;

function createRequest() {
  try {
    request = new XMLHttpRequest();
  } catch (trymicrosoft) {
    try {
      request = new ActiveXObject("Msxml2.XMLHTTP");
    } catch (othermicrosoft) {
      try {
        request = new ActiveXObject("Microsoft.XMLHTTP");
      } catch (failed) {
        request = false;
      }
    }
  }

  if (!request)
    alert("Error initializing XMLHttpRequest!");
}
</script>

上記のようにコードをセットアップする場合は、Ajax の処理を行う前にこのメソッドを呼び出さなければなりません。したがって、リスト 6 のようなコードになります。

リスト 6. XMLHttpRequest 作成メソッドを使用する
<script language="javascript" type="text/javascript">

var request;

function createRequest() {
  try {
    request = new XMLHttpRequest();
  } catch (trymicrosoft) {
    try {
      request = new ActiveXObject("Msxml2.XMLHTTP");
    } catch (othermicrosoft) {
      try {
        request = new ActiveXObject("Microsoft.XMLHTTP");
      } catch (failed) {
        request = false;
      }
    }
  }

  if (!request)
    alert("Error initializing XMLHttpRequest!");
}

function getCustomerInfo() {
  createRequest();
  // Do something with the request variable
}
</script>

このコードで唯一懸念される問題は、エラーの通知が遅れてしまうことです (そしてこの問題が理由で、ほとんどの Ajax プログラマーはこの方法を使用していません)。例えば、フィールドや選択ボックスなどの数が 10 や 15 を数える複雑なフォームがあり、ユーザーが (フォームの下の方にある) フィールド 14 にテキストを入力すると Ajax コードが実行されるとします。この時点で getCustomerInfo() が動作し、XMLHttpRequest オブジェクトを作成しようとして (この例の場合) 失敗します。するとユーザーにはアラートが出され、このアプリケーションは使用できないことが (はっきりと) 伝えられます。しかしこのアラートが出されるのは、ユーザーが時間をかけてフォームにデータを入力した後です。この期に及んでのアラートはかなり苛立たしい話で、普通はこの苛立たしさがユーザーをサイトに引き付ける魅力になるとは言えません。

静的 JavaScript を使用する場合、ユーザーはページにアクセスしようした時点でエラーを受け取ることになります。その場合のエラーも苛立たしいものでしょうか?その特定の Web アプリケーションが自分のブラウザーでは実行されないことにユーザーは腹を立てるかもしれませんが、これと同じエラーが情報の入力に 10 分も費やした後に表示されるよりはましです。この理由 1 つをとっても、コードを静的にセットアップし、問題が発生する可能性がある場合には、早い段階でユーザーに通知することをお勧めします。


XMLHttpRequest による要求の送信

要求オブジェクトが用意できれば、早速要求/応答サイクルを始めることができます。ここで思い出してください。XMLHttpRequest の唯一の目的は、要求を行って応答を受け取れるようにすることです。それ以外のことは (ユーザー・インターフェースの変更、画像の交換、サーバーから返されたデータの解釈) はすべて、ページに含まれる JavaScript、CSS、またはその他のコードが行う作業です。その点を踏まえて、XMLHttpRequest を使用してサーバーに要求を行ってください。

サンドボックスへようこそ

Ajax はサンドボックス・セキュリティー・モデルを使用します。そのため、Ajax コード (特に XMLHttpRequest オブジェクト) が要求を行えるのは、そのコードが実行されているドメインに対してのみです。セキュリティーと Ajax については今後の記事で詳しく説明しますが、この段階では、ローカル・マシンで実行中のコードは、ローカル・マシン上のサーバー・サイド・スクリプトに対してしか要求を行えないことを認識しておいてください。Ajax コードを www.breakneckpizza.com で実行している場合は、www.breakneckpizza.com で実行されるスクリプトに対して要求を行わなければなりません。

サーバーの URL を設定する方法

最初に決定しなければならないのは、接続先サーバーの URL です。これは Ajax に限ったことではなく、接続を行うには URL が必要不可欠です。URL を構成する方法はすでにご存知だと思いますが、ほとんどのアプリケーションでは何らかの静的データのセットに、ユーザーが操作するフォームからのデータを組み合わせて接続先の URL を構成します。リスト 7 に一例として、電話番号フィールドの値を取得し、そのデータを使用して URL を構成する JavaScript を記載します。

リスト 7. 要求 URL を構成する
<script language="javascript" type="text/javascript">
   var request = false;
   try {
     request = new XMLHttpRequest();
   } catch (trymicrosoft) {
     try {
       request = new ActiveXObject("Msxml2.XMLHTTP");
     } catch (othermicrosoft) {
       try {
         request = new ActiveXObject("Microsoft.XMLHTTP");
       } catch (failed) {
         request = false;
       }  
     }
   }

   if (!request)
     alert("Error initializing XMLHttpRequest!");

   function getCustomerInfo() {
var phone = document.getElementById("phone").value;
     var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);
   }
</script>

上記に変わった点は何もありません。このコードはまず phone という変数を新規に作成し、「phone」という ID を持つフォーム・フィールドの値を割り当てます。リスト 8 に、この特定のフォームに対応する XHTML を記載します。ここには、phone フィールドとその id 属性が示されています。

リスト 8. Break Neck Pizza のフォーム
 <body>
  <p><img src="breakneck-logo_4c.gif" alt="Break Neck Pizza" /></p>
  <form action="POST">
   <p>Enter your phone number:
<input type="text" size="14" name="phone" id="phone" 
           onChange="getCustomerInfo();" />
   </p>
   <p>Your order will be delivered to:</p>
   <div id="address"></div>
   <p>Type your order in here:</p>
   <p><textarea name="order" rows="6" cols="50" id="order"></textarea></p>
   <p><input type="submit" value="Order Pizza" id="submit" /></p>
  </form>
 </body>

もう 1 つ注目する点として、ユーザーが電話番号を入力または変更すると、リスト 8 に記載された getCustomerInfo() が呼び出されます。このメソッドが電話番号を取得し、その電話番号を使用して、url 変数に格納される URL ストリングを構成します。前述のように、Ajax コードにはサンドボックス・モデルが適用されるため、接続できるのは同じドメインだけです。したがって、本来は URL にドメイン名を含める必要はありません。この例でのスクリプトの名前は /cgi-local/lookupCustomer.php です。このスクリプト名に、GET パラメーターとして電話番号が追加されます ("phone=" + escape(phone))。

これまで escape() メソッドを目にしたことがない方のために説明しておくと、このメソッドは、プレーン・テキストとして正しく送信できない文字をエスケープするために使用します。例えば、電話番号に含まれるスペースは %20 という文字に変換されて、URL で他の文字と一緒に渡せるようになります。

パラメーターは必要な数だけ追加することができます。別のパラメーターを追加するとしたら、そのパラメーターを URL に追加し、各パラメーターをアンパサンド (&) 文字で区切ればよいだけです (スクリプト名と先頭のパラメーターは、疑問符 (?) によって区切られます)。

要求を開始する方法

果たして open() によって要求は開始されるのか?

open() メソッドが正確に何を実行するかについてはインターネット開発者たちの意見は分かれていますが、このメソッドが何を実行しないのかと言えば、実は要求を開始することです。XHTML/Ajax ページとその接続先スクリプトとの間のネットワークとデータ転送をモニターしてみれば、open() メソッドが呼び出されてもトラフィックは確認できないはずです。なぜこの名前が選ばれたのかは定かでありませんが、あまり適切な選択でなかったことは確かです。

接続先の URL が用意できたら、次は要求を構成します。要求を構成するには、XMLHttpRequest オブジェクトで open() メソッドを使用します。このメソッドは以下の 5 つのパラメーターを引数に取ることができます。

  • request-type: 送信する要求のタイプ。通常の値は GET または POST ですが、HEAD 要求を送信することもできます。
  • url: 接続先 URL。
  • asynch: 要求を非同期にする場合は true、同期要求でなければならない場合は false。このパラメーターはオプションです。デフォルトでは、true に設定されます。
  • username: 認証が必要な場合には、このパラメーターにユーザー名を指定することができます。このオプション・パラメーターには、デフォルト値はありません。
  • password: 認証が必要な場合には、このパラメーターにパスワードを指定することができます。このオプション・パラメーターには、デフォルト値はありません。

通常は上記のうち、最初の 3 つのパラメーターを使用します。非同期要求が必要な場合でも、3 番目のパラメーターのデフォルトの設定には「true」を指定してください。要求が非同期であるかどうかを必ず示すようにしておくと、自己文書化を行う上で便利です。

以上の説明をまとめると、通常はリスト 9 のような行になります。

リスト 9. 要求を開始する
   function getCustomerInfo() {
     var phone = document.getElementById("phone").value;
     var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);
request.open("GET", url, true);
   }

URL が明らかになれば、後は至って簡単です。要求で open() を使用するには、大抵の場合は接続先の URL と併せて GET を使用するだけで十分です (POST を使用しなければならない場合については、今後の記事で説明します)。

非同期性に関する問題

非同期コードの作成および使用方法については、この連載の今後の記事で十分時間をかけて説明する予定ですが、open() の最後のパラメーターがなぜそれほど重要であるかは理解しておく必要があります。通常の要求/応答モデル (つまり、Web 1.0) では、クライアント (ブラウザーまたはローカル・マシンで実行中のコード) はサーバーに対して要求を行います。その要求は同期要求なので、クライアントはサーバーからの応答を待ちます。クライアントが待機している間、通常は以下のいずれか 1 つ、あるいは 2 つ以上の形で待機状態であることが通知されます。

  • 砂時計 (特に Windows)
  • 回転するビーチボール (一般に Mac マシン)
  • 基本的にアプリケーションがフリーズした状態になり、カーソルが時折変化する

このようにまったく対話性が欠けてしまうことが、とりわけ Web アプリケーションの魅力のなさ、あるいは遅さを感じさせる理由です。ボタンをクリックすると、それによって送信された要求に対して応答が返ってくるまで、アプリケーションは原則的に使用できなくなってしまいます。大々的なサーバー処理を必要とする要求を行ったとしたら、かなりの待機時間になる場合もあります (少なくとも現代のマルチプロセッサーや DSL などの待機時間のない世界にとっては顕著な待機時間です)。

その一方、非同期要求はサーバーからの応答を待ちません。要求を送信した後もアプリケーションは動作し続け、ユーザーは相変わらず Web フォームにデータを入力したり、他のボタンをクリックしたりすることができます。さらにはフォームから離れることさえ可能です。回転するビーチボールやクルクル回る砂時計、そして大々的なアプリケーションのフリーズもありません。サーバーは静かに要求に応答し、処理が完了した時点で要求の送信側に処理の完了を知らせます (その方法は、この後すぐ説明します)。その結果、アプリケーションは魅力や速度に欠けているという印象を与えるどころか、応答性、対話性、処理速度に優れているという印象を与えます。これは Web 2.0 の要素の 1 つに過ぎませんが、非常に重要な要素です。どんなに巧みな GUI コンポーネントや Web 設計パラダイムでも、処理速度の遅い同期要求/応答モデルを克服することはできません。

要求を送信する方法

open() を使用して要求を構成したら、その要求を送信する準備は万端です。幸い、要求を送信するメソッドには open() よりも適切な名前が付けられていて、単純に send() と呼ばれます。

send() はパラメーターを 1 つだけ引数に取ります。それは、送信するコンテンツです。このパラメーターについて深く考える前に、URL 自体ですでにデータを送信していることを思い出してください。

var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);

データは send() を使って送信することもできますが、URL 自体に含めて送信することもできます。実際、GET 要求 (通常の Ajax では、GET が使われる場合が約 80 パーセントを占めます) では、URL にデータを含めて送信するほうが遙かに簡単です。セキュリティーで保護する必要のある情報や XML を送信するようになれば、send() でコンテンツを送信することを検討する必要がありますが (セキュリティーで保護する必要のあるデータと XML メッセージングについては、今後の記事で説明します)、send() でデータを渡す必要がなければ、このメソッドへの引数として null を渡すだけで構いません。したがって、この記事を通して引用している例でも、この方法で要求を送信します (リスト 10 を参照)。

リスト 10. 要求を送信する
   function getCustomerInfo() {
     var phone = document.getElementById("phone").value;
     var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);
     request.open("GET", url, true);
request.send(null);
   }

コールバック・メソッドを指定する方法

ここまでの作業では、新しい画期的なことや、非同期的なことはほとんど行っていません。open() メソッドに指定したあの「true」というちょっとしたキーワードで非同期要求をセットアップしたことは確かですが、それ以外の点では、このコードは Java サーブレットと JSP、PHP、あるいは Perl でのプログラミングとよく似ています。では、Ajax と Web 2.0 の大きな秘密とは一体何なのでしょう。その秘密の中心にあるのは、XMLHttpRequest の単純なプロパティー、onreadystatechange です。

まずは、このコードで作成したプロセスを確実に理解してください (必要であれば、リスト 10 をもう一度見てください)。このコードでは、要求がセットアップされて送信されます。これは非同期要求なので、JavaScript メソッド (この例では getCustomerInfo()) はサーバーが処理を完了するのを待機しません。したがってコードは処理を続行し、この例で言うと、メソッドが終了して制御がフォームに戻ります。ユーザーは情報の入力を続けることができ、アプリケーションはサーバーでの処理を待つことはありません。

しかし、ここで興味深い疑問が浮かんできます。サーバーが要求の処理を完了したときには何が起こるのでしょうか。コードが今の状態である限り、何も起こらないというのが、この質問に対する答えです。もちろんそれでは困るので、サーバーには、XMLHttpRequest によって送信された要求の処理が完了した時点で実行すべき内容を指示する必要があります。

JavaScript での関数参照

JavaScript は弱い型付けの言語なので、ほとんどすべてのものを変数として参照することができます。例えば updatePage() という関数を宣言すると、JavaScript はその関数名を変数としても扱います。つまりコードでは、この関数を updatePage という名前の変数として参照することができるのです。

そこで登場するのが、onreadystatechange プロパティーです。このプロパティーには、コールバック・メソッドを指定することができます。コールバックによって、サーバーは (ご想像のとおり) 呼び出し側の Web ページのコードを呼び出すことができます。また、サーバーにはある程度の制御も渡されるため、サーバーは要求の処理を完了すると XMLHttpRequest オブジェクトのなかで特に onreadystatechange プロパティーを調べ、このプロパティーが指定しているメソッドを呼び出します。これをコールバックと呼ぶ理由は、Web ページ自体で何が行われているかに関わらず、サーバーが Web ページに対する呼び出しを開始するからです。例えば、ユーザーが椅子に座っている間、キーボードに触れていなくても、サーバーはコールバック・メソッドを呼び出すことができます。その一方で、ユーザーが入力、マウスの移動、スクロール、ボタンのクリックといった操作を行っている最中でも、ユーザーが何をしているかに関わらず、サーバーはメソッドを呼び出すことができます。

非同期性が活躍するのはここです。非同期の場合、ユーザーがあるレベルでフォームを操作している一方、別のレベルではサーバーが要求に応答した後、onreadystatechange プロパティーによって指定されたコールバック・メソッドを呼び出します。したがって、コードにはコールバック・メソッドを指定しなければならないということです (リスト 11 を参照)。

リスト 11. コールバック・メソッドを設定する
   function getCustomerInfo() {
     var phone = document.getElementById("phone").value;
     var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);
     request.open("GET", url, true);
request.onreadystatechange = updatePage;
     request.send(null);
   }

コード内でこのプロパティーが設定されている場所に注意してください。それは、send() が呼び出される前です。サーバーが要求への応答を完了した時点でプロパティーを参照できるように、このプロパティーは要求が送信される前に設定します。これで残りの作業は、この記事の最後のセクションの焦点となる updatePage() のコーディングだけとなりました。


サーバーからの応答の処理

これまでの流れをまとめると、ユーザーは要求を行った後に (サーバーが要求を処理している間) 引き続き快適に Web フォームでの操作を続けるなか、サーバーが要求の処理を完了します。するとサーバーは onreadystatechange プロパティーを調べ、呼び出すメソッドを認識します。このアプリケーションが非同期であるかどうかに関わらず、この時点で、このアプリケーションに関しては他のすべてのアプリケーションと同じように扱うことができます。つまり、特別な措置を講じてサーバーからの応答に応じるメソッドを作成する必要はないということです。サーバーからの応答に応じて、単にフォームを変更するか、ユーザーを別の URL にリダイレクトするか、あるいはその他必要な何らかの処理をするだけで済みます。このセクションでは、サーバーからの応答に応じて典型的なアクションを行う方法 (ユーザーに表示しているフォームの一部を、フォームの状態はそのままで変更する方法) に焦点を当てます。

コールバックと Ajax

すでに説明したとおり、サーバーに処理の完了時に実行する内容を知らせるには、XMLHttpRequest オブジェクトの onreadystatechange プロパティーを実行対象の関数の名前に設定します。これにより、サーバーは要求の処理を完了した時点で自動的にその関数を呼び出すことになります。そのメソッドに対するパラメーターについても心配する必要はありません。まずはリスト 12 のような単純なメソッドから取り掛かることにしましょう。

リスト 12. コールバック・メソッドのコードを作成する
<script language="javascript" type="text/javascript">
   var request = false;
   try {
     request = new XMLHttpRequest();
   } catch (trymicrosoft) {
     try {
       request = new ActiveXObject("Msxml2.XMLHTTP");
     } catch (othermicrosoft) {
       try {
         request = new ActiveXObject("Microsoft.XMLHTTP");
       } catch (failed) {
         request = false;
       }  
     }
   }

   if (!request)
     alert("Error initializing XMLHttpRequest!");

   function getCustomerInfo() {
     var phone = document.getElementById("phone").value;
     var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);
     request.open("GET", url, true);
     request.onreadystatechange = updatePage;
     request.send(null);
   }

function updatePage() {
     alert("Server is done!");
   }
</script>

上記のコードは、サーバーが処理を完了したことを知らせる重宝なアラートを出すだけです。皆さんのページでこのコードを実行して、ページを保存した後、ブラウザーでページを表示してみてください (この例で使用する XHTML が必要な場合には、リスト 8 を参照してください)。電話番号を入力した後、そのままにしておくと、アラートがポップアップ表示されるはずです (図 3 を参照)。しかし OK をクリックしても、アラートは繰り返しポップアップ表示されます。

図 3. アラートをポップアップ表示する Ajax コード
アラートをポップアップ表示する Ajax コード

使用しているブラウザーによっては、フォームがアラートをポップアップ表示しなくなるまでに、2 回または 3 回、あるいは 4 回もアラートが出されることになります。何が起こっているのかと言うと、その原因は、要求/応答サイクルの重要な要素である HTTP Ready 状態を考慮していなかったことにあります。

HTTP Ready 状態

前に、サーバーが要求の処理を完了すると、呼び出す対象のメソッドを XMLHttpRequestonreadystatechange プロパティーで調べると説明しました。これは事実ですが、すべての真実を語っているわけではありません。実は、サーバーはこのメソッドを、HTTP Ready 状態が変わるたびに呼び出すからです。これが何を意味するかを理解するには、その前に HTTP Ready 状態について理解しなければなりません。

HTTP Ready 状態は要求の状態あるいはステータスを示します。これによって、要求が開始されたかどうか、応答中であるかどうか、または要求/応答モデルが完了したかどうかを判断することができます。また、サーバーが提供した可能性のある応答のテキストやデータを読み込んでも安全かどうかを判断する手掛かりにもなります。Ajax アプリケーションでは次の 5 つの Ready 状態を知っておく必要があります。

  • 0: 要求が初期化されていない状態 (open() を呼び出す前)
  • 1: 要求はセットアップされているが、送信されていない状態 (send() を呼び出す前)
  • 2: 要求が送信され、処理されている状態 (通常はこの時点で、応答からコンテンツ・ヘッダーを取得することができます)
  • 3: 要求が処理されている状態。大抵は応答から部分的なデータを入手可能であるが、サーバーは応答の処理を完了していない状態
  • 4: 応答が完了し、サーバーの応答を取得して使用できる状態

ほとんどすべてのクロスブラウザーの問題と同様に、これらの Ready 状態の使い方には多少一貫性が欠けています。Ready 状態は常に 0 から 1、2、3、4 の順に移行すると期待するかもしれませんが、実際にはこの順序で移行することはまれです。ブラウザーによっては 0 や 1 がレポートされることなく、いきなり 2 から 3、そして 4 へと進みます。すべての状態をレポートするブラウザーの場合でも、Ready 状態 1 を数回レポートする場合もあります。前のセクションで説明したように、サーバーは updatePage() を数回呼び出し、呼び出すたびにアラート・ボックスがポップアップ表示される結果となりました。これは意図した動作ではないはずです。

Ajax プログラミングの場合、直接対処しなければならない状態は、サーバーからの応答の受信が完了し、応答に含まれるデータを確認して使用できる状態であることを示す Ready 状態 4 だけです。この状態になったことを認識するには、コールバック・メソッドの最初の行をリスト 13 のように変更します。

リスト 13. Ready 状態を確認する
   function updatePage() {
if (request.readyState == 4)
       alert("Server is done!");
   }

この変更によって、サーバーが実際に処理を完了していることを確認することができます。この Ajax コードを実行してみると、アラート・メッセージが表示されるのは 1 回限りとなります。これが、正常な動作です。

HTTP ステータス・コード

リスト 13 のコードは明らかに正常に機能するものの、まだ問題が残っています。サーバーが要求に応答して処理を完了した結果、エラーをレポートしたらどうなるでしょうか。思い出してください。サーバー・サイドのコードでは、Ajax によって呼び出されているのか、それとも JSP、通常の HTML フォーム、あるいはその他のタイプのコードによって呼び出されているのかを判断しますが、情報をレポートする方法としては、従来の Web 固有の方法しかありません。そして Web の世界では、要求で発生するさまざまな事態に HTTP ステータス・コードで対処することができます。

例えば、ある URL に対する要求を入力する際に、その URL が誤って入力されたとすると、ページが見つからないことを示す 404 エラー・コードを受け取ります。これは、HTTP 要求が受け取る可能性のある多くのステータス・コードのうちの 1 つでしかありません (すべてのステータス・コードを網羅したリストへのリンクについては、「参考文献」を参照してください)。セキュリティーで保護する必要のあるデータまたは禁止されているデータにアクセスしていることを示す 403 と 401 も一般的なエラー・コードで、どちらも処理が完了した応答によって生じるコードです。別の言葉に置き換えると、サーバーは要求を最後まで実行したけれども (つまり HTTP Ready 状態が 4)、サーバーが返すデータはおそらくクライアントが期待するものではないということです。

したがって、Ready 状態だけではなく、HTTP のステータスも確認しなければなりません。ここで期待するコードは、単に問題がないことを意味するステータス・コード 200 です。Ready 状態が 4 で、ステータス・コードが 200 であれば、サーバーのデータを処理することが可能であり、そのデータが (エラーやその他の問題のある情報ではなく) 要求したデータであるということになります。リスト 14 に記載するように、もう 1 つのステータス・チェックをコールバック・メソッドに追加してください。

リスト 14. HTTP ステータス・コードを確認する
   function updatePage() {
     if (request.readyState == 4)
if (request.status == 200)
         alert("Server is done!");
   }

さらに確実な、ただし複雑さは最小限に抑えたエラー処理を追加するには、例えばこの他のステータス・コードのチェックを 1 つか 2 つ追加します。変更を加えた updatePage() を見てください (リスト 15)。

リスト 15. 簡単なエラー・チェックを追加する
   function updatePage() {
     if (request.readyState == 4)
       if (request.status == 200)
         alert("Server is done!");
else if (request.status == 404)
         alert("Request URL does not exist");
       else
         alert("Error: status code is " + request.status);
   }

getCustomerInfo() のなかで定義した URL を存在しない URL に変更して、どうなるかを確かめてください。要求を行うと、要求された URL が存在しないことを通知するアラートが表示されます。これで完璧です。すべてのエラー条件に対処するには到底及びませんが、それでもこの単純な変更によって、標準的な Web アプリケーションで発生する可能性のある問題の 80 パーセントに対処することができます。

応答テキストを読み取る方法

これで、(Ready 状態により) 要求が完全に処理されること、そして (ステータス・コードにより) サーバーが通常の良好な応答を返すことが確実になったので、いよいよサーバーから返されたデータの処理に取り掛かれます。都合のよいことに、サーバーからのデータは XMLHttpRequest オブジェクトの responseText プロパティーに保存されています。

responseText に保存されるテキストのフォーマットや長さに関しての詳細は、意図的に曖昧にされています。これは、サーバーがこのテキストを実質的に何にでも設定できるようにするためです。例えばカンマで区切られた値を返すスクリプトもあれば、パイプ記号 (|) で区切られた値を返すスクリプトや、1 つの長いテキスト・ストリングを返すスクリプトもあります。どのような形で返すかは、すべてサーバー次第です。

この記事で使用している例の場合、サーバーは顧客の最終注文と顧客の住所をパイプ記号で区切って返します。そしてこの注文と住所の両方を使用して、フォーム上の要素の値が設定されます。リスト 16 に、フォームの表示を更新するコードを記載します。

リスト 16. サーバーの応答を処理する
   function updatePage() {
     if (request.readyState == 4) {
       if (request.status == 200) {
var response = request.responseText.split("|");
         document.getElementById("order").value = response[0];
         document.getElementById("address").innerHTML =
           response[1].replace(/\n/g, "
");
} else alert("status is " + request.status); } }

このコードではまず、JavaScript の split() メソッドにより、responseText が抽出されてパイプ記号で区切られます。それによって生成された値の配列が、response に保存されます。最初の値である顧客の最終注文は、配列の response[0] としてアクセスされ、ID が「order」に設定されたフィールドの値として設定されます。配列の 2 番目の要素である response[1] の値は顧客の住所ですが、これには少しばかりの追加の処理が必要です。住所に含まれる行は通常の行区切り記号 (「\n」文字) で区切られているため、コードはこれらの区切り記号を XHTML スタイルの行区切り記号である <br /> に置き換える必要があります。そのために使用するのは、replace() 関数と正規表現です。そして最後に、変更されたテキストが HTML フォームの div に含まれる内部 HTML (innerHTML) として設定されます。その結果、フォームは突如、図 4 に示すように顧客の情報で更新されるというわけです。

図 4. 顧客データを受け取った後の Break Neck フォーム
顧客データを受け取った後の Break Neck フォーム

今回の記事を締めくくる前に、XMLHttpRequest のもう 1 つの重要なプロパティーである responseXML に触れておきます。サーバーが XML で応答することを選択した場合には、(ご想像のとおり) このプロパティーに XML 応答が含まれます。XML 応答の処理は、プレーン・テキストを処理する方法とはかなり異なり、構文解析、DOM (Document Object Model)、そしてその他の考慮事項が関わってきます。XML については今後の記事で説明しますが、responseXMLresponseText 関連の話題によく登場するので、今から触れておく価値はあるはずです。多くの単純な Ajax アプリケーションの場合には responseText だけで十分ですが、Ajax アプリケーションで XML を処理する方法についても、まもなく学ぶことになります。


まとめ

読者の皆さんは、XMLHttpRequest に少しうんざりしていることでしょう。私も 1 つのオブジェクト、しかもこれほど単純なオブジェクトだけを取り上げた記事を読むことはめったにありません。しかし、このオブジェクトは Ajax を使用するページやアプリケーションを作成するときには何度も繰り返し使用します。実を言うと、XMLHttpRequest については、説明することがまだかなり残っているので、この後の記事でも、要求で GET だけではなく POST を使用する方法、要求とサーバーからの応答のコンテンツ・ヘッダーを設定したり、読み込んだりする方法を説明します。これらの方法を学ぶうちに、要求をエンコードし、さらには要求/応答モデルで XML を処理する方法も理解できるようになるはずです。

だいぶ先の話になりますが、この連載ではよく使われている Ajax ツールキットについても取り上げる予定です。実は、これらのツールキットはこの記事で説明した詳細のほとんどを抽象化し、Ajax プログラミングをさらに容易にします。ツールキットを手軽に利用できるというのに、なぜここまで下位レベルの詳細をコーディングしなければならないか疑問に思うかもしれませんが、アプリケーションで何が行われているのかを理解していなければ、アプリケーションで発生している問題を突き止めるのは非常に難しいからです。

ですから、これらの詳細を無視したり、先を急いだりしないでください。手軽で素晴らしいツールキットがエラーを引き起こしたときに、どうしたらよいのかわからずに頭をかきむしり、サポートを求めて E メールを送信することはしたくないはずです。直接 XMLHttpRequest を使用する方法を理解していれば、かなり奇異な問題でも簡単にデバッグして修正することができます。ツールキットが役に立つのは、問題の対処をツールキットに任せ切りにしなければの話です。

重要なのは、XMLHttpRequest を使いこなせるようになることです。ツールキットを使用した Ajax コードを実行する場合でも、XMLHttpRequest とそのプロパティーおよびメソッドだけを使用してコードを書き直してみることをお勧めします。非常に効果的な演習となるだけでなく、コードの内容をさらに深く理解できるようになるはずです。

次回の記事では、このオブジェクトをさらに掘り下げ、今回よりも特殊なプロパティー (responseXML など) をいくつか取り上げて詳しく探るとともに、POST 要求を使用してさまざまなフォーマットでデータを送信する方法を説明します。今からコードの作成に取り掛かって、約 1 ヶ月後の記事に備えてください。

参考文献

学ぶために

  • Ajax をマスターする: Ajax の紹介」(developerWorks、2005年12月) を読んで、Web サイト構築の生産的手法、Ajax について理解してください (この記事にリストされた参考文献を調べるだけでも、アクセスする価値はあります)。
  • WebSphere Portal による Ajax の使用」(developerWorks、2006年6月) を読んで、ポータルのパフォーマンスを向上させ、よりクリーンなポータル・アプリケーション・アーキテクチャーを作成する方法、そして最も重要な点として、遙かに応答性に優れたポータルをユーザーに提供する方法を学んでください。
  • Building Dynamic Java Applications」(developerWorks、2005年9月) では、Java パースペクティブを使って、サーバー・サイドからの視点で Ajax を説明しています。
  • Call SOAP Web services with Ajax」(developerWorks、2005年10月) は、Ajax を既存の SOAP ベース Web サービスに統合する方法を説明した、かなり高度な記事です。
  • Google GMail は Web が機能する仕組みを変える Ajax アプリケーションの素晴らしい例です。
  • Google ベースの Web 2.0 アプリケーションとしては、Google Maps もあります。
  • Flickr は、Ajax を使用して Web ベースのアプリケーションにデスクトップの感覚をもたらしている優れた例です。
  • Why Ajax Matters Now」を読んで、Ajax が (現在) 重要となっている理由を理解してください。
  • Microsoft の Internet Explorerを使用している場合は、Microsoft Developer Network の XML デベロッパー・センターで耳よりな情報を入手できます。
  • 応答に含まれる可能性のあるすべての HTTP ステータス・コードのリストを調べてください。
  • developerWorks Web development ゾーンでは、多種多様な Web ベースのソリューションを取り上げた記事を専門に揃えています。
  • Head First HTML with CSS & XHTML』(Elizabeth Freeman、Eric Freeman 共著、O'Reilly Media, Inc.、2005年12月) では、XHTML、CSS、そしてこの 2 つを組み合わせる方法を詳細に説明しています。

製品や技術を入手するために

  • Head Rush Ajax ― 学びながら読む Ajax 入門』(Elisabeth Freeman、Eric Freeman、Brett McLaughlin 共著、オライリー・ジャパン、2006年2月) で、この記事で取り上げた概念を Head First スタイルで吸収してください。
  • Java & XML 第 2 版』(Brett McLaughlin 著、オライリー・ジャパン、2001年4月) には、XHTML および XML 変換に関する本著者の説明が記載されています。
  • JavaScript』(David Flanagan 著、オライリー・ジャパン、2001年11月) では、JavaScript と動的 Web ページの操作について広範に説明しています。次回の改版では、Ajax についての 2 つの章が追加されます。

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Web development, XML, Java technology
ArticleID=438144
ArticleTitle=Ajax をマスターする: 第 2 回 JavaScript と Ajax を使用して行う非同期要求
publish-date=01172006