はじめに
今更ですが最近ajaxを少し触るようになりました。
jQueryを使えば簡単にajax通信ができるため、単純なものであればそれほど実装は難しくないかと思います。
ですが少し複雑なことをやろうとしてみると、急にハードルが上がる気がします。
そこで自戒を込めて、ajax初心者の方に対してはじめにこれを知りたかったと思えるんじゃないかというものをまとめてみたいと思います。
※jQueryを利用したajax通信を想定しています
目次
- 処理の順番を意識する
- $.ajax()の省略メソッドを知る
- イベントを知る
- デバッグ方法を知る
- 通信状況を想定する(タイムアウトを設定する)
- 連続クリックを防止する ← [New!]
- 連続通信を防止する ← [New!]
処理の順番を意識する
ajaxを使用する場合は、非同期通信で処理したいケースがほとんどかと思います
しかし単純にそのまま書いただけでは処理の順番は保証されず、予期せぬエラーが起こりえます。メンテナンス性を保持しつつ、シーケンスな処理を行うには一工夫必要です。
オプションで
async: false
を指定すれば同期処理も可能です
$.Deferred()を使った直列処理
直列処理を実現するために、ここでは$.Deferred()
を使った例を取り上げたいと思います。
【ポイント】
- 通信成功後のコールバックで
resolve()
を行う - Deferredを設定した関数では必ず
promise()
を返す - 直列処理を行うには
then()
で関数をつなぐ(thenは新しいpromiseを返す)
function showLog1() {
var dfd = $.Deferred();
$.ajax({
type: 'get',
url: './data1.html'
})
.done(function(returnData) {
// 返ってくるのに時間が掛かる処理
setTimeout(function() {
console.log(returnData);
dfd.resolve();
}, 1000);
});
// fail()は省略
return dfd.promise();
}
function showLog2() {
var dfd = $.Deferred();
$.ajax({
type: 'get',
url: './data2.html'
})
.done(function(returnData) {
console.log(returnData);
dfd.resolve();
});
// fail()は省略
return dfd.promise();
}
/*
showLog1();
showLog2();
だとdata2, data1の順番で返ってきてしまう
*/
/*
data1, data2が交互に返ってくる
*/
showLog1()
.then(showLog2)
.then(showLog1)
.then(showLog2);
[補足]
$.ajax()
自体がpromiseを返すため、$.ajax()を使う場合はそのままreturnするだけでもOK(コメント参照&コメントありがとうございます!)
thenの中の関数で引数を使いたい
then()
に渡す関数に引数を使いたい場合は、function()を渡して実現することができます。
showLog1()
.then(function() {
return showLog2('文字列');
})
.then(showLog1)
.then(showLog2);
ajax()で返ってきたデータをそのまま使いたい
then(function())
の第一引数には取得したデータが入ってくるためそのまま利用することもできます。
function addTextToData(data) {
return 'This is Ajax: ' + data;
}
function getData(url) {
return $.ajax({
type: 'get',
url: url
});
}
getData('./data.html').then(function(data) {
var text = addTextToData(data);
console.log(text); // This is Ajax: データ1だよ
});
$.Deferred()
を使う場合は、resolve(arg)
に引数を渡すことで同じようにthen(function(data))
で利用できます
$.when()
を使った並列処理
$.when()
と組み合わせることで並列処理も加えることができます。
例えば「AとBの順番はどっちでも良いが、Cはその2つが先に実行されていないとダメ」といったことも可能です。
// 最初の4つは非同期に処理される
$.when(
showLog1(),
showLog2(),
showLog1(),
showLog2()
)
// 以降は順番に処理される
.then(showLog1)
.then(showLog2)
.then(showLog1)
.then(showLog2);
$.ajax()の省略メソッドを知る
$.get()
や$.post()
などは、$.ajax()
の一部オプションを指定した省略系になります。
そのため$.ajax()
でも同じ動作を実現できますが、あえて省略メソッドを使うことでコードの意図・可読性が上がるかも知れません。
$.get()と$.post()
それぞれ$.ajax()
の一部オプションを指定した状態と同じ動作です。
$.getJSON()と$.getScript()
それぞれ$.ajax()
の一部オプションを指定した状態と同じ動作です。
$.ajaxSetup
(※省略形ではありませんが)個別のajaxでオプションが設定されていない時に、割り当てられるデフォルト値を設定できます。
イベントを知る
ajaxリクエストに対してイベントを設定することができます。
$(document).on('ajaxSend', function(){
console.log('ajaxリクエスト送信します');
});
注意事項
- jQuery1.8より使用できるのはdocumentのみになりました
-
$.ajax()
や$.ajaxSetup()
のオプションに、global: false
を設定すると、そのリクエストではイベント実行されなくなります
イベント一覧
-
ajaxComplete()
: ajaxリクエストが完了する度に発動 -
ajaxError()
: ajaxリクエストが失敗した際に発動 -
ajaxSend()
: ajaxリクエストが送信される度に発動 -
ajaxStart()
: ajaxリクエストが開始される際に発動 -
ajaxStop()
: ajaxリクエストが完了した際に発動 -
ajaxSuccess()
: ajaxリクエストが成功する度に発動
ajaxStart()
,ajaxStop()
は一連の通信、複数のリクエストをまとめて実行するときに使用します(そのため引数はありません)。詳細はドキュメントなどを参照ください
デバッグ方法を知る
ここではGoogle ChromeのDevtoolsを使った方法を一つ取り上げてみます。
ajaxのというより非同期処理のデバッグ方法ですがSourceタブのCall StackパネルのAsyncにチェックを入れるだけで、非同期処理を含んだ呼び出し元関数を記録することができます。
テキストだけだと何言ってるのかわからねぇ状態かもしれませんので、Gifアニメを作成してみました。
- 関数の名前も順番もぐちゃぐちゃで複雑に入り組んだ現代社会のようなコードがありました
- でもdevtoolsのbreakpointsを使えば、どのような流れで処理されているか分かります
- でも非同期処理がされていると・・・
- ※シンプルにするためajaxではなく
setTimout()
を使っています
- そんな時はAsyncにチェックを入れてみよう!
通信状況を想定する(タイムアウトを設定する)
すでにQiitaにドンピシャな記事がありましたので、そちらをご紹介させていただきます。
連続クリックを防止する
ユーザアクションによってajax処理を行う場合、クリック連打などで同じ通信が何度も発生すると困ることは多いと思います。サーバ側で制御することも可能かもしれませんが、できればフロント側で制御したいところです。
色々とやり方はあるかと思いますが、一番シンプルで簡単な方法はフラグとなる変数を用意して実行を判断することでしょうか。
var allowAjax = true;
function addItem() {
if (allowAjax) {
allowAjax = false;
// ボタン自体を無効にしているので、allowAjaxが無くても実際には連続クリックできませんが...
$('button#trigger').prop('disabled', true).text('通信が終わるまで押せないよ');
$.ajax({
type: 'get',
url: './data.html'
})
.done(function(returnData) {
// 通信に一秒掛かるとする
setTimeout(function() {
$('#addArea').append('<li>' + returnData + '</li>');
$('button#trigger').prop('disabled', false).text('通信開始');
allowAjax = true;
}, 1000);
});
}
}
$('#trigger').on('click', function() {
addItem();
});
連続通信を防止する
これは通信中に再度通信処理が発生したら直前のものを中断し、結果的にフロント側では最後の通信処理のみを実行するようにする、というものです(不要になったデータはjs側で処理しない)。
ここでは jqXHR
を利用しています。
jqXHRとは
JavaScriptの組み込みオブジェクトである、XMLHttpRequestをjQueryでパワーアップしたようなもの
$.ajax()
はjqXHRを返すDeferredのところで
$.ajax()
はpromiseを返すと書きましたが、正確には$.ajax()
はjqXHRを返し、jqXHRオブジェクトはpromiseを備えていると、表現した方が良いかもしれません。
var jqxhr = null;
function addItem() {
if (jqxhr) {
// 通信を中断する
// ただしfail(), always()は実行される
jqxhr.abort();
}
jqxhr = $.ajax({
type: 'get',
url: './data.html'
})
.done(function(returnData) {
$('#addArea').append('<li>' + returnData + '</li>');
});
}
$('#trigger').on('click', function() {
addItem();
});
[動作イメージ]
※回線が遅い状況を想定するため、devtoolsでNetwork速度を落としています
[補足]
リクエスト回数を減らすものではないことにご注意ください
JavaScript: abort()の動作を勘違いしてました
無駄な通信を止める
jqXHR
を利用したajax通信の中断は、上記のクリック以外でも色々と応用を効かせることができそうです。
例えば、フォームなどで入力された値に対してajaxで通信を行っている途中で、再度入力値が変更された場合には、abort()
を実行してやれば、その時点で不要となった前のデータでの通信処理を中断する、といったこともできそうです。