【JavaScript入門】Ajaxと非同期処理

## Ajaxの基礎

### 概要

* 非同期通信、Asynchronous JavaScript + XMLの略
* サーバー側と非同期通信を行い、受け取った結果をDOM経由でページに反映するしくみ
* Ajaxを利用すれば、サーバーが処理中でもクライアント側で操作を継続できる

### Ajaxの利点

* 操作性の改善
  * 通信ごとに発生していたページのチラツキを解消
  * サーバーが処理中もクライアント側では処理を継続できる
* パフォーマンスの向上
  * ページの必要な部分だけを更新するので、通信量を最小限に抑えられる
  * サーバーの処理終了を待つ必要がないので、そもそもの体感速度が向上する
* 開発生産性／運用性の向上
  * リッチなユーザーインターフェイスをブラウザー標準の技術だけで構築できる
  * 動作に特別なプラグインを必要としないので、導入が容易である

### 基本的な実装手順
* XMLHttpRequestオブジェクトを生成
* サーバー通信時の処理を定義
* 非同期通信を開始

### XMLHttpRequestオブジェクト
* 非同期通信を管理
* クライアント／サーバー間の汎用的な通信を担当するオブジェクト
* XML、HTTPに限定されるわけではない

## クロスオリジン通信を実装するための手法
Ajaxの基盤となるXMLHttpRequestオブジェクトでは、セキュリティ上の理由から、クロスオリジン通信が制限されている。そのため、異なるサイトで提供されているサービスに通信するためには以下のいずれかの方法で実装する必要がある。

* プロキシ
    * 外部サービスにアクセスするための仕組みをサーバーサイドで用意する
* Access-Control-Allow-Originの設定
    * 最新のXMLHttpRequestオブジェクトではクロスオリジン通信に対応しているが、サーバーがAccess-Control-Allow-Originをヘッダーで明示的に許可していなければならない。たとえばPHPであれば、以下のように記述する。InternetExplorerではバージョン10以降が必要。
    
    ```php
    <?php
    // "http://www.examples.com/"からのアクセスを可能にする
    // すべてのドメインからのリクエストを許可したい場合には、URLを"*"とする
    header('AccessControlAllowOrigin:http://www.examples.com/');
    ```

* JSONP
* クロスドキュメントメッセージング


## 非同期処理

非同期の処理が必要とされるもの

* ネットワーク経由のリクエスト（Ajax）
* ファイルシステム関連の操作（ファイルの読み書き）
* 意図的に遅延された操作（アラームなど）

## コールバック

JavaScriptのコールバックは「将来のある時点で呼び出される関数」のことを指す

``` javascript:script.js
console.log("setTimeoutの前: " + new Date());
function f() {
    console.log("関数fの中: " + new Date());
}
/*
 * setTimeout（組み込みの関数）は指定の時間だけ実行を遅延
 * setTimeoutは第2引数に指定された時間（1マイクロ/1000秒単位）が経過してから、
 * 第1引数に指定された関数を呼び出します。
 */
setTimeout(f, 10 * 1000); // 10秒後にfを実行
console.log("setTimeoutの後" + new Date());

// 無名関数を利用
console.log("setTimeoutの前：" + new Date());
setTimeout(
  function() {
    console.log("setTimeoutに指定された無名関数の中：" + new Date());
  },
  10 * 1000 /* setTimeoutの第2引数 */
);
console.log("setTimeoutの後" + new Date());

// アロー関数を利用
console.log("setTimeoutの前：" + new Date());
setTimeout(() => console.log("アロー関数の中：" + new Date()), 10 * 1000);
console.log("setTimeoutの後" + new Date());
```

### コールバックの問題点

非同期処理がいくつも連なる場合、コールバック関数では入れ子が深くなりすぎて、1つの関数が肥大化する傾向にある。このような問題をコールバック地獄という。

## Promise

* コールバックの欠点を補うために考案されたもの
* 通常より安全で保守しやすいコードになる
* コールバックを不要にしてくれるわけではない
* コールバックだけだと見つかりにくいようなバグやわかりにくい記述をなくしてくれる
* 非同期の処理をプロミスで「ラップする（包み込む）」ことで、見通しがよくなる

### 概要

* Promiseは完了される（成功）か棄却される（失敗）のいずれか
* 完了されてから後で棄却されたり、複数の結果が起きたりすることはない
* 完了されれば一度だけ完了される、棄却されれば一度だけ棄却される
* オブジェクトとして非同期処理をまとめてくれているため簡単に受け渡しができる

![Promise_001.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326104/606dc3ef-de6f-3bdd-8114-0c4c73e7a237.jpeg)

![Promise_002.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326104/6f40b432-181d-8a25-ef16-f61f548e4393.jpeg)

### サンプルコード

``` javascript
/*
 * resolveやrejectedを複数回呼び出してもエラーにはならないが、
 * 最初の呼び出しだけが意味をもつ
 */
function countdown(seconds) {
  return new Promise(
    function(resolve, rejected) {
      for(let i = seconds; i >= 0; i--) {
        setTimeout(
          function() {
            if(i > 0) console.log(i + '...');
            else resolve(console.log("GO!"));
          },
          (seconds - i) * 1000
        );
      }
    }
  ); 
}

/*
 * 関数countdownを呼び出す側では、新しく生成されたPromiseを受け取る
 * Promiseが返ってきたら次の２つの引数を指定してメソッドthenを呼び出す
 * - 成功のときに行う処理を記述した関数
 * - 失敗のときに行う処理を記述した関数（省略可能）
 * 呼び出されるのはどちらか一方だけ
 */
countdown(5).then(
  function(mgs) {
    // 成功時に行う処理を記述
    console.log("resolve: " + msg);
  },
  function(error) {
    //失敗時に行う処理を記述
    console.log("rejected" + err.message);
  }
);
```

エラー処理の取り扱い

```javascript:script.js
function countdown(seconds, error) {
  return new Promise(function(success, failure) {
    const ids = [];
    for(let i = seconds; i >= 0; i--) {
      ids.push(setTimeout(
        function() {
          if(i === 13) {
            ids.forEach(clearTimeout);
            failure(new Error(`${error}秒`))
          } else if(i > 0) {
            console.log(i + '...');
          } else {
            success("GO!");
          }
        },
        (seconds - i) * 1000
      ))
    }
  });
}

countdown(5, 13).then(
  function(msg) {
    console.log("success: " + msg);
  },
  function(err) {
    console.log("error: " + err.message);
  }
);
```

``` javascript
/*
 メソッドcatchを使用する方法
 処理を成功の場合と失敗の場合の２つに分けることができる
 */
const p = countdown(5);
p.then(function() { 
  // 成功時に行う処理を記述
  console.log("カウントダウン成功");
});
p.catch(function(err) {
  // 失敗時に行う処理を記述
  console.log("カウントダウンでエラーが起こった：" + err.message);
});
```