$.Deferred() について理解してみる

## ■概要
以下不明点を解消したいので調べました
- JavaScriptのコードで`$.Deferred()`って出てくるけどこれなんだっけ
- `then()`ってメソッドがたくさん連なっているがなにを表しているのか
- `resolve()`や`promise()`や`reject()`とはなんだろう
- なんとなく非同期処理関係っぽいけど...。

## ■調べたこと
### 〇まずは、Deferredの意味
`defer（延期する）の過去形。延期された、先送りされた。`

### 〇`$.Deferred()` 概要
参照：https://api.jquery.com/jQuery.Deferred/
- `$.Deferred()`を省略せずに書くと`jQuery.Deferred()`
- jQueryで用意されている関数（jQuery 1.5 で導入された）
- ファクトリ関数。以下のメソッドを持つオブジェクトを返してくれる
    - 複数のコールバック関数をキューに登録する
    - 登録したコールバック関数を呼び出す
    - 同期または非同期関数の成功または失敗の状態を中継する

## ■ここまでの理解
・`$.Deferred()`は、オブジェクトを用意する関数
・そのオブジェクトは、登録した「コールバック関数」の実施タイミングや順番を操るメソッドをもつ

## ■では、具体的な使い方は？
### 〇`$.Deferred()`が返してくれるオブジェクトのメソッドについて
参照：https://api.jquery.com/category/deferred-object/
- 以下のようなメソッドがあるようだ(※非推奨 になっているものは省きました)
    - `jQuery.Deferred()`
    Deferredオブジェクトを返すファクトリ関数

    - `jQuery.when()`
    0 個以上の Thenable objects (通常は非同期の Deferred オブジェクト) に基づいてコールバック関数を実行する方法を返す
    
    - `deferred.always()`
    引数の関数を、Deferredオブジェクトが「解決」か「拒否」されたときに呼び出す関数に追加する
    
    - `deferred.catch()`
    引数の関数を、Deferredオブジェクトが「拒否」されたときに呼び出す関数に追加する
    
    - `deferred.done()`
    引数の関数を、Deferredオブジェクトが「解決」されたときに呼び出す関数に追加する
    
    - `deferred.fail()`
    引数の関数を、Deferredオブジェクトが「拒否」されたときに呼び出す関数に追加する
    
    - `deferred.notify()`
    指定された引数を使用して、Deferredオブジェクトの progressCallbacks を呼び出す。
    
    - `deferred.notifyWith()`
    指定されたコンテキストと引数を使用して、Deferredオブジェクトの progressCallbacks を呼び出す。
    
    - `deferred.progress()`
    notify()やnotifyWith()で呼び出すprogressCallbacksを追加する
    
    - `deferred.promise()`
    Deferred の Promise オブジェクトを返す
    
    - `deferred.reject()`
    Deferredオブジェクトを「拒否」し、指定された引数を使用して failCallbacks を呼び出す
    
    - `deferred.rejectWith()`
    Deferredオブジェクトを「拒否」し、指定されたコンテキストと引数を使用して failCallbacks を呼び出す
    
    - `deferred.resolve()`
    Deferredオブジェクトを「解決」し、指定されたコンテキストと引数を使用して doneCallbacks を呼び出す
    
    - `deferred.resolveWith()`
    Deferredオブジェクトを「解決」し、指定されたコンテキストと引数を使用して doneCallbacks を呼び出す

    - `deferred.state()`
    Deferredオブジェクトの現在の状態（"pending"か"resolved"か"rejected"）を返す
    
    - `deferred.then(doneFilter, [failFilter, progressFilter])`
    Deferredオブジェクトのステータスごとに呼び出す関数を追加する
        - Deferredオブジェクトが「解決」の時は doneFilter
        - Deferredオブジェクトが「拒否」の時は failFilter
        - Deferredオブジェクトが「進行中」の時は progressFilter

    - `.promise()`
    キューに登録されているかどうかに関係なく、コレクションにバインドされている特定のタイプのすべてのアクションが完了したかどうかを確認するための Promise オブジェクトを返す

### 〇実際実装して確かめた
#### ★resolveとthen
1. Deferredオブジェクトを作って`resolve`することで、直後の`then()`に進む
1. `resolve()`の引数「5」は、直後の`then()`の、１つ目の引数の関数(=doneFilter)に渡している
1. 1つめの`then()`の処理が終わったら次の`then()`に進む
1. `then()`の戻り値`value * 2`が次の`then()`の引数として渡る
```javascript:javascript
$.Deferred().resolve(5) // 1. 2.
  .then(function (value) { 
    return value * 2; // 4.
  })
  .then(function (value) { // 3.
    console.log(value);
  });
  // ⇒ コンソールに「10」が出力される
```
#### ★promise, resolve, reject, notify
1. Deferredオブジェクトを作り
1. ステータスを設定し
1. promiseオブジェクトを返している
- この関数の呼び出しに`then()`をつなげることで、ステータスごとの処理をこの処理の後に実行させることができる
```javascript:javascript
  function asynchronousMethod() {
    // 1.
    var deferred = $.Deferred();
    var random = Math.floor(Math.random() * 10); // 0~9のランダムな数値
    // 2.
    if (random < 3) {
      deferred.resolve('Success'); // 偶数の場合は成功にする
    } else if (random < 6) {
      deferred.reject('Failure'); // 奇数の場合は失敗にする
    } else {
      deferred.notify('Notify'); // それ以外の場合は通知する
    }
    // 3.
    return deferred.promise(); // Promiseオブジェクトを返す
  }
```

#### ★上記非同期関数(asynchronousMethod)の利用、done, fail, progress, always
1. `asynchronousMethod`で返ってきたステータスによって実行される関数が決まる
1. 成功(`resolve`)の場合は1番目の引数関数
1. 失敗(`reject`)の場合は2番目の引数関数
1. 通知(`notify`)の場合は3番目の引数関数
1. 上記非同期関数のステータス設定時に引数に設定された値は、それぞれの関数の引数(value)で受け取る
1. 1つめの`then()`の結果で、成功であれば`done`、失敗であれば`fail`、通知であれば`progress`が実行される
1. alwaysは「解決」か「拒否」の時に実行されるので、「通知」の場合は実行されない
- 1つめの`then()`の失敗時の関数(failCallbacks)で、`return $.Deferred().reject();`がなければ、その後は`done()`の関数が実行される
```javascript:javascript
asynchronousMethod()
  .then( 
    function (value) {console.log('成功1：' + value)}  // 2.
    , function (value) {  // 3.
        console.log('失敗1：' + value);
        return $.Deferred().reject(); // 失敗の時に失敗を返すようにしている
    } 
    , function (value) {console.log('通知1:' + value)} // 4.
  ).done(                                 // 6.
    function () {console.log('成功3')}
  ).fail(
    function () {console.log('失敗3')} 
  ).progress(
    function () {console.log('通知3')}
  )
  .always(function () { // 7.
    console.log('完了');
  });
```

#### ★when
- `when()`は、`deferred1`と`deferred2`両方でステータス設定されたら後続が実行される
- 「5秒かかる処理」と「7秒かかる処理」は平行で実行されるので、最後の「全ての処理が完了しました」と出るのはより時間のかかる方の処理とほぼ同じ時間(約7秒)になる。5秒+7秒=12秒ではない。
```javascript:javascript
let starttime = new Date().getTime();
var deferred1 = $.Deferred();
var deferred2 = $.Deferred();

$.Deferred().resolve()
.then(function () {
  // 5秒かかる処理
  setTimeout(function () {
    let countdiff1 = new Date().getTime() - starttime; // 約5秒
    console.log('deferred1:', countdiff1);
    deferred1.resolve();
    
  }, 5000);
});
$.Deferred().resolve()
.then(function () {
  // 7秒かかる処理
  setTimeout(function () {
    let countdiff2 = new Date().getTime() - starttime; // 約7秒
    console.log('deferred2:', countdiff2);
    deferred2.resolve();
  }, 7000);
});

$.when(deferred1, deferred2).done(function () {
  let countdiff3 = new Date().getTime() - starttime;  // 約7秒
  console.log('全ての処理が完了しました', countdiff3);
});
```

#### ★whenその2
- whenは、引数のdeferredのいずれかが失敗であれば、後続は失敗時の処理が実行される
```javascript:javascript
let nowtimestamp = new Date().getTime();
var deferred1 = $.Deferred();
var deferred2 = $.Deferred();
var random = Math.floor(Math.random() * 10);

$.Deferred().resolve()
.then(function () {
  setTimeout(function () {
    deferred1.reject();
  }, 5000);
});
$.Deferred().resolve()
.then(function () {
  setTimeout(function () {
    deferred2.resolve();
  }, 7000);
});

$.when(deferred1, deferred2).then( 
  function () {console.log('成功：', new Date().getTime() - nowtimestamp)}
  , function () {console.log('失敗：', new Date().getTime() - nowtimestamp)} // こっち
);
```

## ■まとめ
- $.Deferred()は、Deferredオブジェクト を用意する関数
- Deferredオブジェクトは、登録した「コールバック関数」の実施タイミングや順番を操るメソッドをもつ
- 非同期関数同士が複数あるときに、並列で実行させたり直列で実行させたり制御することができる
### ステータス設定するメソッド
|メソッド|引数|概要|
| ---- | ---- | ---- |
|resolve|型自由の値<br>(後続に値を渡す)|ステータスを成功にする|
|reject|〃|ステータスを失敗にする|
|notify|〃|ステータスを通知にする|
### コールバック関数用意するメソッド
|メソッド|引数|概要|
| ---- | ---- | ---- |
|then|成功時実行関数<br>失敗時実行関数<br>通知時実行関数|つながってる直前の処理のステータス(成功/失敗/通知)ごとに呼び出すコールバック関数を用意|
|done|成功時実行関数|つながってる直前の処理のステータスが成功のときに呼び出すコールバック関数を用意|
|fail|失敗時実行関数|つながってる直前の処理のステータスが失敗のときに呼び出すコールバック関数を用意|
|progress|通知時実行関数|つながってる直前の処理のステータスが通知のときに呼び出すコールバック関数を用意|
|always|成功or失敗時実行関数|つながってる直前の処理のステータスが成功/失敗どちらかの時に共通して呼び出すコールバック関数を用意|
### Promiseオブジェクトを返すメソッド
|メソッド|引数|概要|
| ---- | ---- | ---- |
|promise|オブジェクト<br>(Promise にできる)|Promiseオブジェクトを返す|

### 複数のDeferredオブジェクトを監視しているメソッド
|メソッド|引数|概要|
| ---- | ---- | ---- |
|when|Deferred<br>オブジェクト|引数のオブジェクト全てでステータス設定されたら後続実行|