Promiseのメモ - その4: asyncとawait 

asyncとawaitの仕様を調べます。EcmaScript 2017(8th Edition)で追加された機能です。

## 基本的な使い方

awaitは、Promiseオブジェクトを返す関数を並べて実行する、つまり同期を取りつつ実行するのに使うようです。ただし、awaitはasyncを付けた関数の中でしか使えません。

次の例では、Promiseを返すmakePromiseを並べて、前にawaitを付けています。`await makePromise(a)`の実行が終わってから、`await makePromise(b)`が順に実行されるはずです。

```javascript
function makePromise(num) {
  return new Promise((resolve, reject) => {
    if(num % 2 == 0)
      resolve('OK');
    else
      reject('NG');
    console.log(`executer called: ${num}`);
  });
}

(async (a, b) => {
   let x = await makePromise(a);
   let y = await makePromise(b);
   console.log(`async function finished: ${x} ${y}`);
})(2, 4);
console.log("async function called");
```

```text:結果
executer called: 2
async function called
executer called: 4
async function finished: OK OK
```

アロー関数が読みにくければ、次のように書いても同じです。

```javascript
let afunc = async function(a, b) {
  let x = await makePromise(a);
  let y = await makePromise(b);
  console.log(`async function finished: ${x} ${y}`);
};
afunc(2, 4);
console.log("async function called");
```

普通の関数定義の前にasyncを付けることもできます。

```javascript
async function afunc(a, b) {
  let x = await makePromise(a);
  let y = await makePromise(b);
  console.log(`async function finished: ${x} ${y}`);
}
afunc(2, 4);
console.log("async function called");
```

## awaitの働き

上記の例では、「executer called: 2」が実行されて少し経ってから「executer called: 4」が実行されています。async関数の中では順番に動いていますが、外側から見ると非同期的な動きです。

awaitは次の働きをしているようです。

- Promiseオブジェクトの前にawaitを付けると、履行済みになるまで待つ。
- 「await Promiseオブジェクト」は履行済み結果の値を返す。

thenやcatchはPromiseオブジェクトを返すので、次のようにするとxに`"OKOK"`が入ります。

```javascript
let x = await makePromise(a).then(x => x + x);
```

async関数の中にさえあれば、awaitはどんな式の前にでも付けられるようです。次にようにしてもエラーになりません。zにはtrueかfalseが入ります。

```javascript
let z = await (a == b);
```

履行済みになるまで待つので、次のようにresolveを呼ばない場合は、2つ目のmakePromiseはいつまでも実行されません。async関数を呼び出しても、async関数の中身の実行は終了しません。

```javascript
function makePromise(num) {
  return new Promise((resolve, reject) => {
    console.log(`executer called: ${num}`);
  });
}

(async (a, b) => {
  let x = await makePromise(a);
  let y = await makePromise(b);
  console.log(`async function finished: ${x} ${y}`);
})(2, 4);
console.log("async function called");
```

```text:結果
executer called: 2
async function called
```

## asyncが返すもの

次のように、asyncを付けて作った関数が返すものと、その関数呼び出しが返すものを調べます。

```javascript
let afunc = async (a, b) => {
};
console.log(afunc.constructor);
console.log(afunc(2, 4).constructor);
```

```text:結果(Chrome)
ƒ AsyncFunction() { [native code] }
ƒ Promise() { [native code] }
```

asyncを付けて作った関数は、FunctionオブジェクトではなくAsyncFunctionオブジェクトになります。Functionと違って、AsyncFunctionでは`new AsyncFunction()`とすると「ReferenceError: AsyncFunction is not defined」が出ます。つまり、AsyncFunctionという名前は内部的にだけ使われています。

async関数に()を付けて呼び出すと、Promiseオブジェクトが返ります。ということは、async関数の呼び出しに対してthenやcatchが使えるということです。

次の例は、関数makeAsyncPromiseでasyncが作ったPromiseを返し、そのPromiseに対してthenとcatchをつなげています。async関数の戻り値が、履行済み結果の値になることが分かります。

```javascript
function makePromise(num) {
  return new Promise((resolve, reject) => {
    if(num % 2 == 0)
      resolve('OK');
    else
      reject('NG');
    console.log(`executer called: ${num}`);
  });
}

function makeAsyncPromise(a1, b1) {
  return (async (a2, b2) => {
    let x = await makePromise(a2);
    let y = await makePromise(b2);
    return x + y;
  })(a1, b1);
}

makeAsyncPromise(2, 4).then((x) => {
  console.log(`then callback: ${x}`);
}).catch((r) => {
  console.log(`catch callback: ${r}`);
});
console.log('then and catch called');
```

```text:結果
executer called: 2
then and catch called
executer called: 4
then callback: OKOK
```

## 例外

async内では、try - catchを使って例外またはrejectの結果を処理できます。

```javascript
(async (a, b) => {
  try {
    let x = await makePromise(a);
    let y = await makePromise(b);
    console.log(`async function finished: ${x} ${y}`);
  }
  catch(e) {
    console.log(`error: ${e}`);
  }
})(2, 1);
```

```text:結果
executer called: 2
executer called: 1
error: NG
```

async関数が返すPromiseに対してcatchを呼べば、try - catchを使う必要はなくなります。executerのrejectや、コールバック関数、async関数で発生した例外は、すべて最後のcatchで処理できます。

```javascript
function makeAsyncPromise(a1, b1) {
  return (async (a2, b2) => {
    let x = await makePromise(a2);
    let y = await makePromise(b2);
    return x + y;
  })(a1, b1);
}

makeAsyncPromise(2, 1).then((x) => {
  console.log(`then callback: ${x}`);
}).catch((r) => {
  console.log(`catch callback: ${r}`);
});
console.log('then and catch called');
```

```text:結果
executer called: 2
then and catch called
executer called: 1
catch callback: NG
```

## すべてを同期するのは無理

ここまで長々と書いてきたのは、最近[Puppeteer](https://github.com/GoogleChrome/puppeteer)の使い方を調べたときに、async、awaitがよく分からなかったからです。今どきのJavaScriptライブラリではPromiseオブジェクトを返す関数が用意されてて、awaitを付けて使うものらしいですね。

次のような書き方で「1→2→3」の順に実行することはできないのか、というのが研究の目的でしたが、**できない**ことが分かりました。

```javascript
console.log("1");
(async (a, b) => {
  let x = await makePromise(a);
  let y = await makePromise(b);
  console.log("2");
})(2, 4);
console.log("3");
```

asyncとawaitを使っても、結局のところ非同期で動くPromiseが次々にできていくだけなので、最終的にはコールバック関数を使うしかありません。JavaScriptの1つの流れの中でPromiseすべてを順番に動かすのは無理です。

```javascript
console.log("1");
(async (a, b) => {
  let x = await makePromise(a);
  let y = await makePromise(b);
  console.log("2");
})(2, 4).then((x) => {
  console.log("3");
});
```

以上です。気が向いたら追加するかもしれません。

- [Promiseのメモ - その1: resolveとthen](https://qiita.com/kazutosato/items/41639c3877a83b9ecee2)
- [Promiseのメモ - その2: チェーン、例外](https://qiita.com/kazutosato/items/94118b30ddf6c26cd24f)
- [Promiseのメモ - その3: 小ネタ](https://qiita.com/kazutosato/items/b3afd079d45352e98574)


## 参考文献

- [ECMAScript® 2017 Language Specification (ECMA-262, 8th edition, June 2017)](https://www.ecma-international.org/ecma-262/8.0/)
- [async function - JavaScript | MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function)