Deferredな $.ajax をラップしてコールバック関数(done/fail)に共通処理を追加する

## Deferred でない場合

コールバック処理を従来の ```success```, ```error```, ```complete``` で指定する方法。
こんな感じでプロジェクト特有の共通処理などを追加していることが多いと思います。

### 実装

```javascript

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);
};

```

### 使い方

```javascript
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)を注入できるようにするのがミソ

```javascript
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());
};
```

### 使い方

```javascript
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オブジェクトを生成しない場合

つまり、

```javascript
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!
```

え？いいんじゃないの？
でも、こんな時ダメ

```javascript
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を切り上げられない。
これはこう書いたのと同じ

```javascript
$.ajax({
// ...
}).done(function() {
	if (data.isXXX) {
		console.log('何かをハンドリングして、この場合はここでdoneを終わりたい！');
		return;
	}
}).done(function() {
	console.log('DONE!');
})...
```

2つのdoneが別々のコールバック処理としてイベントキューに入れられて順番(=FIFO)に実行されるため。

#### ② ラッパーがreturnするDeferred(Promise)オブジェクトのmerge順が逆

もし、


```javascript
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```)を実行できるようにして何の問題もなさそうだけど...

```javascript
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)オブジェクト相当になっている。

![Pentan 2.png](https://qiita-image-store.s3.amazonaws.com/0/3899/81db566b-87b3-4a7f-5760-18b047564669.png "Pentan 2.png")

そこで下記のように```defer.resolveWith```でコールバック関数内でのthisのコンテキストを明示的に指定してあげると...


```javascript
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相当に👍

![Pentan.png](https://qiita-image-store.s3.amazonaws.com/0/3899/d11489b8-677a-338c-9a34-defc4b5650d7.png "Pentan.png")