はじめに
別投稿でも明かしているが、現在javascript環境でテキストエディタを作成しており、その機能実現のためいろいろ調べた中で、いまさらながら知ったjavascriptの最新事情もあった。
今回はその中で今後最も使いそうなAjaxリクエストに関する機能(API)、Fetch APIを自分の覚書を兼ねてまとめたい。
Ajaxリクエストの変遷
ここでは別に世界的な歴史等を紹介するつもりはなく、あくまで自分自身がこれまで使ってきた感覚としてのAjaxリクエストを実現するための機能を振り返る。
XMLHttpRequestや自前のAjax関数時代
誰もが解説サイトを読んで実装したことがあるであろう時代のコード。大体次のような感じかと思う。
function myAjax() {
var http = null;
try{
http = new ActiveXObject('Msxml2.XMLHTTP')
}catch(e){
try{
http = new ActiveXObject('Microsoft.XMLHTTP')
}catch(e){
http = new XMLHttpRequest()
}
}
return http;
}
まだActiveXObjectを使っていた頃は'Msxml2.XMLHTTP'や'Microsoft.XMLHTTP'などを呼び出して実現していた。そのうち、どのブラウザもXmlHttpRequestを安定して実装したのでほぼ一択になったのではないかと思う。
しかしこれをそのまま使っていた頃は、通信を呼び出す処理と呼び出した後結果を受け取って加工する処理が明らかにバラけていて、一連の機能としてまとめるのに苦労していた記憶がある。
//受け取って加工する
http.onreadystatechange = function () {
if (http.readyState == 3) { //処理中
}else if (http.readyState == 4) { //処理完了
if (http.status == 200) {
}
}
}
//呼び出す
http.open("GET",url,async);
http.send("");
//---POSTの例
http.open("POST",url,true);
http.setRequestHeader("Content-type","application/x-www-form-urlencoded");
http.send(data);
jQuery全盛期
初期のAjaxライブラリを押しのけて台頭したjQueryがトップを走るようになりその機能も安定してくると、おそらくどのサイトや利用者もjQuery内に用意されたAjax通信機能を使っていたのではないかと思う。
また、Promiseの考え方を知っているか否かで使い勝手が変わる。
//PromiseやDeferredの考え方を知らなかった頃は次のようにしていた。
$.ajax({
"url" : "http://hoge.foobar",
"method" : "POST",
"data" : {
"p" : "text",
"src" : "src",
"tar" : "dst"
},
success : function (data){
},
failure : function (err) {
}
});
//知ってからはこんな感じ
var ret = $.ajax({
"url" : "http://hoge.foobar",
"method" : "POST",
"data" : {
"p" : text,
"src" : src,
"tar" : dst
}
});
ret.then(function(data){},function(err){});
もしかすると最近のウェブアプリ開発者の方はもはや$.ajaxなど一切使っていないのかもしれない。自分はつい最近まで、というか現在進行系で未だに使っていた。
HTML5でやれFile APIだのクラウドプラットフォームのAPIを使うだの割と本当に目的のみの部分だったり派手な部分だけ気にかけていて、Ajaxリクエスト通信もすでに時代が進んでいたことに気づかなかった。
まさか、Fetch APIなるものが存在するとは。
Fetch APIに触れる
Fetch APIについてのガチの解説は MDNのFetch API が詳しい。
ここでは自分が実際に使った感触での紹介に留めたい。
APIの各機能
Fetch APIといっても、Fetchというクラスがあるわけではなく、実際には大きく次の3つに分かれている。
- Headers
- Request
- Response
それぞれを呼び出す・受け取らせる中心的存在がfetch()関数とのこと。fetch()を介して通信し、fetch()から結果を受け取る流れになっている。
厳密な説明は他の方にお願いするとして、初めて使う方は次のような捉え方でいいのかもしれない。
$.ajax = fetch()
RequestやResponseはこのための引数・結果用オブジェクトという捉え方も加えておこう。
リクエストを準備する
fetch()にはRequestを受け渡すようになっている。Requstは一般的なインスタンスのように作成すればよい。
var req = new Request(url,{
method: "post",
body : ...
});
fetch(req).then(...)
Requestの第一引数はURLである。
第二引数はJSON形式のオブジェクトで、様々な引数を追加で指定するようになっている。ここでmethodにGETやPOSTをセットしたり、headersやbodyでリクエストする内容を指定できる。
//POSTでデータを送信する場合
//---XMLHttpRequestの頃
var data = ...; // 送るために諸々気にしなければいけない配慮を含む
http.send(data);
//---$.ajaxの頃
$.ajax(url,{
data : {
"hoge" : "one",
"foo" : true,
"bar" : 123
}
});
//Fetch APIのRequest
var req = new Request(url,{
body : data
});
非常に簡単に書いているが、大体こんな感じだという感覚だけ掴んでいただけれればいいだろう。Requestでは'body'というプロパティにセットするのがjQueryとの違いの一つだ。間違えないように注意したい。
そしてこのbodyにセットする形式はMDNを見ると次のようになっている。
body: リクエストに追加する本文で、 Blob, BufferSource, FormData, URLSearchParams, USVString, ReadableStream オブジェクトが使用できます。なお、リクエストが GET 又は HEAD メソッドを使用している場合、本文を持てません。
最近のjavascriptではこんなにいろんな形式が実装されているのか・・・と辟易してしまう。
とりあえず今までどおりに{...}のJSON形式を指定して試したら、$.ajaxのコードと異なりうまく通信がされず失敗してしまった。
なるべく無理なく従来の言語風に近い記述で置き換えられないか、それぞれの形式を調べてみた。
var prm = new URLSearchParams();
prm.append("hoge","one");
prm.append("foo",true);
prm.append("bar",123);
prm.delete("foo"); //あるデータを削除する場合
console.log(prm.get("bar")); //あるデータを参照する場合
...
var req = new Request(url,{
method : "POST",
body : prm
});
...
このURLSearchParamsなるクラスが、名称的にも使い勝手的にも一番スマートでその場に合っていると判断。この手のクラスによくあるgetやdelete、has、forEachなどあるので知識の流用的にも困らない。
なお、MDNにもあるように、method=GETの場合はこの'body'は使えないそうなので注意したい。
レスポンスを受け取る
リクエストの準備ができたら早速実行してみる。fetch()にRequestのインスタンスを噛ませればOKだ。あとは返ってくるレスポンスの処理を繋げるだけ。(実はfetchの引数はRequestと同じものが指定可能)
このfetch()、返すのはResponseを返すPromiseだ。つまりjQuery.ajaxと同じようなことができる。
...
fetch(req).then(function(response){
if (response.ok) {
console.log(response.url); //レスポンスのURL
console.log(response.status); //レスポンスのHTTPステータスコード
}
});
//または・・・
fetch(url,{
method : "POST",
body : prm
}).then(function(response){
if (response.ok) {
console.log(response.url); //レスポンスのURL
console.log(response.status); //レスポンスのHTTPステータスコード
}
});
Responseはokやstatusなどのプロパティが備わっているので、それらで状態を判別をまず行う。
そしてそれぞれの形式のデータを受け取る。Responseが返す型は次の通り。
- ArrayBuffer
- Blob
- FormData
- JSON
- Text
それぞれ返す型がそのままメソッド名になっており、それらはPromiseを通してそれぞれの型のデータを返してくれる。
fetch(req).then(function(response){
if (response.ok) {
response.blob().then(function(blob){
console.log(blob);
});
response.formData().then(function(formData){
console.log(formData);
});
response.text().then(function(text){
console.log(text);
});
}
});
//---jQueryの頃は...
$.ajax(url,{
...
dataType : "json", //実行する前に型を指定していた(あるいは自動判別)
...
}).then(function(data){
console.log(data);
});
jQuery.ajaxとは使い勝手は異なるが、返す型が複数ありそれぞれで処理を分けたいという場合は割とスマートな使い勝手になるのかもしれない。
また、その型のデータを受け渡すのはPromiseのため、新たにPromiseや$.Deferred()を挟まなくても、アプリケーション側で連携しやすいのはポイント高い。
終わりに
あくまで自分が試して理解できた範疇なので、多くの方には物足りないかもしれない。しかしこれから使ってみるという方々は既存のテクニックと見比べてみて、あぁこうなるのかという感触を掴んでもらえたらいいだろう。
各ブラウザ標準で$.ajax的なことが存分に利用できるならそれに越したことはない。デスクトップ向けのブラウザではほぼ利用可能みたいだが、モバイル向けブラウザではRequestとResponseで未対応が多いので気をつけよう。
なお、自分がアプリ開発しているElectron(つまりNode.js)とUWPの環境では問題なく使えたため、自作アプリでは積極的に活用していきたい。