Deferred でない場合
コールバック処理を従来の success
, error
, complete
で指定する方法。
こんな感じでプロジェクト特有の共通処理などを追加していることが多いと思います。
実装
var myAjax = function(arg) {
var opt = $.extend({}, $.ajaxSettings, arg);
opt.success = (function(func) {
return function(data, statusText, jqXHR) {
console.log('success時の共通処理 ...');
if (func) {
func(data, statusText, jqXHR);
}
};
})(opt.success);
opt.error = (function(func) {
return function(jqXHR, statusText, errorThrown) {
console.log('error時の共通処理 ...');
if (func) {
func(jqXHR, statusText, errorThrown);
}
};
})(opt.error);
opt.complete = (function(func) {
return function(jqXHR, statusText) {
console.log('complete時の共通処理 ...');
if (func) {
func(jqXHR, statusText);
}
};
})(opt.complete);
return $.ajax(opt);
};
使い方
myAjax({
url: '/hoge',
type: 'GET',
dataType: 'json',
data: {
param1: 'value1',
param2: 'value2'
},
success: function(json, statusText, jqXHR) {
console.log('SUCCESS!');
},
error: function(jqXHR, statusText, errorThrown) {
console.log('ERROR!');
},
complete: function(jqXHR, statusText) {
console.log('COMPLETE!');
}
});
実行結果
success時の共通処理 ...
SUCCESS!
complete時の共通処理 ...
COMPLETE!
Deferred な場合
コールバック処理をPromiseの done
, fail
, always
で指定する方法。
実装
- ポイント
- jQuery1.8以降、$.ajax の結果(jqXHR)は
XMLHttpRequest
のプロパティ・メソッドに加えて、Promise
のプロパティ・メソッドも実装している - return 時、(myAjax内で生成した) DeferredオブジェクトをPromise化( defer.promise() )し、外部からは then()、done()、fail() といったメソッド以外、Deferredオブジェクトを操作出来ないようにする
- return 時、(myAjax内で生成した) Deferredオブジェクトに $.ajax の結果(jqXHR) をマージすることで、元々のjqXHRのプロパティ・メソッドを使用できるのはそのままに、外部から処理(done、fail)を注入できるようにするのがミソ
- jQuery1.8以降、$.ajax の結果(jqXHR)は
var myAjax = function(arg) {
var opt = $.extend({}, $.ajaxSettings, arg);
var jqXHR = $.ajax(opt);
var defer = $.Deferred();
jqXHR.done(function(data, statusText, jqXHR) {
console.log('done(=success)時の共通処理 ...');
// defer.resovle ではなくて defer.resolveWith で
// myAjax(...).done() 内でのthisのコンテキストを
// 明示的に指定する
defer.resolveWith(this, arguments);
});
jqXHR.fail(function(jqXHR, statusText, errorThrown) {
console.log('fail(=error)時の共通処理 ...');
// defer.reject ではなくて defer.rejectWith で
// myAjax(...).fail() 内でのthisのコンテキストを
// 明示的に指定する
defer.rejectWith(this, arguments);
});
jqXHR.always(function() {
console.log('always(=complete)時の共通処理 ...');
});
return $.extend({}, jqXHR, defer.promise());
};
使い方
myAjax({
url: '/hoge',
type: 'GET',
dataType: 'json',
data: {
param1: 'value1',
param2: 'value2'
}
}).done(function(json, statusText, jqXHR) {
console.log('DONE!');
}).fail(function(jqXHR, statusText, errorThrown) {
console.log('FAIL!');
}).always(function() {
console.log('ALWAYS!');
});
実行結果
done(=success)時の共通処理 ...
DONE!
always(=complete)時の共通処理 ...
ALWAYS!
ハマったポイント
① ラッパー内でDeferredオブジェクトを生成しない場合
つまり、
var myAjax = function(arg) {
var opt = $.extend({}, $.ajaxSettings, arg);
var jqXHR = $.ajax(opt);
jqXHR.done(function(data, statusText, jqXHR) {
console.log('done(=success)時の共通処理 ...');
});
jqXHR.always(function() {
console.log('always(=complete)時の共通処理 ...');
});
return jqXHR;
};
myAjax({
// ...
}).done(function(json, statusText, jqXHR) {
console.log('DONE!');
}).always(function() {
console.log('ALWAYS!');
});
だとどうなるか?
こうなる。
done(=success)時の共通処理 ...
DONE!
always(=complete)時の共通処理 ...
ALWAYS!
え?いいんじゃないの?
でも、こんな時ダメ
var myAjax = function(arg) {
var opt = $.extend({}, $.ajaxSettings, arg);
var jqXHR = $.ajax(opt);
jqXHR.done(function(data, statusText, jqXHR) {
if (data.isXXX) {
console.log('何かをハンドリングして、この場合はここでdoneを終わりたい!');
return;
}
});
jqXHR.always(function() {
console.log('always(=complete)時の共通処理 ...');
});
return jqXHR;
};
結果。
何かをハンドリングして、この場合はここでdoneを終わりたい!
DONE! ←★この処理は実行したくない★
always(=complete)時の共通処理 ...
ALWAYS!
done/failの共通処理内でdone/failを切り上げられない。
これはこう書いたのと同じ
$.ajax({
// ...
}).done(function() {
if (data.isXXX) {
console.log('何かをハンドリングして、この場合はここでdoneを終わりたい!');
return;
}
}).done(function() {
console.log('DONE!');
})...
2つのdoneが別々のコールバック処理としてイベントキューに入れられて順番(=FIFO)に実行されるため。
② ラッパーがreturnするDeferred(Promise)オブジェクトのmerge順が逆
もし、
var myAjax = function(arg) {
var opt = $.extend({}, $.ajaxSettings, arg);
var jqXHR = $.ajax(opt);
var defer = $.Deferred();
// ...
// return $.extend({}, jqXHR, defer.promise());
return $.extend({}, defer.promise(), jqXHR);
};
とすると、defer
のdoneやfailがjqXHR
のそれらで上書きされてしまうため、①と同じ結果になる。
③ ラッパーのdone/failのコールバック関数内で参照するthisのコンテキスト
下記のようにdefer.resolve
でコールバック関数(done
)を実行できるようにして何の問題もなさそうだけど...
var myAjax = function(arg) {
var opt = $.extend({}, $.ajaxSettings, arg);
var jqXHR = $.ajax(opt);
var defer = $.Deferred();
jqXHR.done(function(data, statusText, jqXHR) {
console.log('done(=success)時の共通処理 ...');
// ★ ↓↓↓ ★
defer.resolve(data, statusText, jqXHR);
// ★ ↑↑↑ ★
});
// ...
return $.extend({}, jqXHR, defer.promise());
};
myAjax({
// ...
}).done(function() {
// ↓ このthisは何者??
console.log(this);
})...
myAjax実行時のdone
のコールバック内のthisを見てみると
myAjax内で生成したDeferred(Promise)オブジェクト相当になっている。
そこで下記のようにdefer.resolveWith
でコールバック関数内でのthisのコンテキストを明示的に指定してあげると...
var myAjax = function(arg) {
var opt = $.extend({}, $.ajaxSettings, arg);
var jqXHR = $.ajax(opt);
var defer = $.Deferred();
jqXHR.done(function(data, statusText, jqXHR) {
console.log('done(=success)時の共通処理 ...');
// ★ ↓↓↓ ★
defer.resolveWith(this, arguments);
// ★ ↑↑↑ ★
});
// ...
return $.extend({}, jqXHR, defer.promise());
};
myAjax({
// ...
}).done(function() {
// ↓ このthisは何者??
console.log(this);
})...
バッチリ元々の$.ajaxのthis相当に👍