JavaScriptの非同期処理について

#JavaScriptの非同期処理についてまとめてみた

##非同期処理とは？
まず、普通のプログラムを考えます
通常のプログラムだと

```js
console.log("aiueo");
console.log("kakikukeko");

aiueo
kakikukeko
```

このように上から順にプログラムが実行されていきます。
これを同期的な処理と言います

では次のようなプログラムはどうでしょうか？

```js
setTimeout(
  () => {
    console.log("aiueo");
  }, 5 * 1000
)

console.log("kakikukeko");

kakikukeko
aiueo
```
この場合は5秒後に最初のconsole.logが実行されます。
このようにあるタスク(setTimeoutの処理)が実行されているときに他のタスク(console.log("kakikukeko"))が実行されるような処理を非同期処理と言います。

##JavaScriptの非同期処理

JavaScriptの非同期処理の方法はいくつかあり、大まかに<b>callback時代</b>,<b>ジェネレーター時代</b>,<b>Promise</b>時代<b>async,await</b>時代に分けられます、

この記事では全ては紹介しませんが、callbackとpromiseを重点的にまとめていきたいと思います。


##callback時代

callbackは先ほど紹介したサンプルのようにsetTimeoutやsetIntervalなどの関数を使って非同期処理をすることを言います。

###スコープ
非同期の処理をする際、スコープに注意する必要があります。
例えば

```js
function countdown() {
  let i;
  console.log("count down");
  for(i = 5; i >= 0; i--) {
    setTimeout(
      () => {
        console.log(i === 0 ? "GO!" : i);
      },(5 - i) * 1000
    )
  }
}

countdown();
```
このような処理を考えて見ましょう

この場合は-1が6回出力されて終了します。forループが実行され値が-1となるのです。
そしてその後にcallbackが実行されます。
これはcountdownが実行されるときに変数iを含む[クロージャー](http://qiita.com/takeharu/items/4975031faf6f7baf077a)が生成されますforループ内では非同期的に全て同じ変数iにアクセスしています。

しかし、この関数内の遅延時間の計算の部分は同期的に行われる((5-i)*1000の部分)ので5秒間のカウントダウンは正常に行われるのです。
setTimeoutの呼び出し自体も同期的に行われています。

##Promise時代

###Promiseの生成
Promiseの生成はPromiseの引数に非同期処理の関数を渡して、インスタンスを生成するだけです

```js
new Promise(<Some Ajax Function>);
```

上の\<Some Ajax Function\>には二つの引数を指定します。

```js
new Promise(function(onFulfilled, onRejected) {
  //* some ajax process *//
})
```
上記のような感じで指定します。
onFulfilledは処理が成功したときの処理、
onRejectedは処理が失敗したときの処理です。

最初に提示したcountdownの例をPromiseベースに変えて見ます.

```js
function countdown(seconds) {
  new Promise((onSuccess, onFailed) => {
    for(let i = 0; i <= seconds; i++) {
      setTimeout(() => {
        console.log(`${i}秒経過`);
      }, i * 1000)
    }
  });
} 
```

このようにPromiseを生成します。

###Promiseの利用
上記の例ではcountdown();を実行できますが、それでは戻されるPromiseを利用していないので意味がありません
では、Promiseを利用する処理を考えてみましょう。

```js
function countdown(seconds) {
  return new Promise((onSuccess, onFailed) => {
    for(let i = 0; i <= seconds; i++) {
      setTimeout(() => {
        console.log(`${i}秒経過`);
        if(i === 5)
          onSuccess();
      }, i * 1000)
    }
  });
} 

countdown(5).then(
  () => { //*成功した時onSuccessの処理
    console.log("カウントダウン成功！");
  },
  (err) => { //*失敗した時onFailedの処理
    console.log(err);
  }
)
```

関数countdownを実行するとPromiseが返され、Promiseのメソッドthenが実行されます
そこでPromiseの中の処理が成功したらthenの第一引数の関数がonSuccessに渡され、失敗したら第二引数の関数がonFailedの渡されるのです。

その他にPromiseには失敗した時用にcatchメソッドが用意されており、これを使うことによって以下のように表現することもできます。

```js
const p = countdown(5);
p.then(() => {
  console.log("カウントダウン成功");
}).catch((err) => {
  console.log("err");
});
```

エラーを出したい時は以下のようにします。

```js
function countdown(seconds) {
  return new Promise((onSuccess, onFailed) => {
    for(let i = 0; i <= seconds; i++) {
      setTimeout(() => {
        console.log(`${i}秒経過`);
        if(i === seconds)
          onSuccess();
        if(i === 3)
          return onFailed(new Error("数字の3はカウントできません"));
      }, i * 1000)
    }
  });
} 

const p = countdown(5);
p.then(() => {
  console.log("カウントダウン成功");
}).catch((err) => {
  console.log("err");
});

```

### Promiseのチェイニング
Promiseは.thenを繋げてチェイニングできます。

例

```js
function countdown(seconds) {
  return new Promise((onSuccess, onFailed) => {
    const timeoutIds = [];
    for(let i = 0; i <= seconds; i++) {
      timeoutIds.push(setTimeout(() => {
        if(i === 13) {
          timeoutIds.forEach(clearTimeout); //カウントダウン取り消し
          return onFailed(new Error("13は不吉な数字です"));
        } else if (i === 15) {
          onSuccess(console.log("カウントダウン成功"));
        }
        console.log(`${i}秒経過`);
      }, i * 1000));
    }
  });
}

function launch() {
  return new Promise((onSuccess, onFailed) => {
    console.log("発射!");
    setTimeout(() => {
      onSuccess("周回軌道に乗った!!"); //次のthenの引数msgに渡される
    }, 2 * 1000);
  });
}

countdown(15)
  .then(launch)
  .then((msg) => {
    console.log(msg);
  })
  .catch((err) => {
    console.log(err);
  });

```

上記例のようにチェイニングできます。

##async, await

関数の前にasyncを付けることでasync functionにすることができます。
演算子awaitはasync function内で使うものでPromiseが成功するまで待ちます。

例

```js
const fs = require('fs');

function readF(filename) {
  return new Promise((onSuccess, onFailed) => {
    fs.readFile(filename, "utf-8", (err, data) => {
      err ? onFailed(err) : onSuccess(data);
    });
  });
}

function writeF(filename, data) {
  return new Promise((onSuccess, onFailed) => {
    fs.writeFile(filename, data, err => {
      err ? onFailed(err) : onSuccess('OK');
    });
  });
}

async function readAndWrite() {
  try {
    let writeData = await readF("a.txt"); // a.txtを読み込んでwriteDataへ代入
    writeData += await readF("b.txt"); // b.txtを読み込んでwriteDataへ追加
    await writeF("d.txt", writeData); // d.txtへ書き込み
  } catch (err) {
    console.error(err);
  }
}

readAndWrite();

```

```a.txt
a
```

```b.txt
b
```

結果

```d.txt
a
b
```

非同期処理を行うreadF,writeF関数それぞれでawaitを使って、Promiseがresolveするまで待っています。
try catchを使ってエラー処理をすることもできます。


## まとめ
この記事ではJavaScriptの非同期処理のうち、callbackとpromiseについて扱ってきました。
ミスがあったらご指摘お願いしますm(__)m

参考 [初めてのJavaScript](https://www.amazon.co.jp/%E5%88%9D%E3%82%81%E3%81%A6%E3%81%AEJavaScript-%E7%AC%AC3%E7%89%88-ES2015%E4%BB%A5%E9%99%8D%E3%81%AE%E6%9C%80%E6%96%B0%E3%82%A6%E3%82%A7%E3%83%96%E9%96%8B%E7%99%BA-Ethan-Brown/dp/4873117836/ref=sr_1_1?ie=UTF8&qid=1502972383&sr=8-1&keywords=%E3%81%AF%E3%81%98%E3%82%81%E3%81%A6%E3%81%AEjavascript)