async/await の例外処理まとめ

こんがらがったので、ひと通りのパターンを検証してみた。

## まとめ

* async function をただ呼び出すときは `try catch` は意味をなさない
* `await` 付きで呼び出せば `try catch` が意味をなす
* `await` の有無にかかわらず `.catch(e => {})` で拾うことができる

## 確認環境

* Node.js
    * v8.9.4

## 基本（例外なしの挙動）

```js
// 100ms 後に resolve される Promise を返す
const asyncFunction = (v) => new Promise(r => setTimeout(() => r(v), 100));

const nothing = async () => {
    asyncFunction('nothing');
};

const justReturn = async () => {
    return asyncFunction('just return');
};

const returnWithAwait = async () => {
    return await asyncFunction('return with await');
};

(async () => {
    console.log(nothing());
    console.log(justReturn());
    console.log(returnWithAwait());

    console.log(await nothing());
    console.log(await justReturn());
    console.log(await returnWithAwait());
})();

```

```:実行結果
Promise { undefined }
Promise { <pending> }
Promise { <pending> }
undefined
just return
return with await
```

async function は Promise を返す。
async function 内での await の有無は影響しない。

## async function 内での例外処理

```js
// 100ms 後に reject される Promise を返す
const asyncFunction = (v) => new Promise((_, r) => setTimeout(() => r(v), 100));

const nothing = async () => {
    asyncFunction('nothing');
};

const justAwait = async () => {
    await asyncFunction('just await');
};

const justTryCatch = async () => {
    try {
        asyncFunction('just try catch');
    } catch (e) {
        console.error(e);
    }
}

const tryCatchWithAwait = async () => {
    try {
        await asyncFunction('try catch with await');
    } catch (e) {
        console.error(e);
    }
}

const justCatch = async () => {
    return asyncFunction('just catch').catch(e => console.error(e));
}

const awaitCatch = async () => {
    return await asyncFunction('await catch').catch(e => console.error(e));
}

nothing();
justAwait();
justTryCatch();
tryCatchWithAwait();
justCatch();
awaitCatch();
```

```:実行結果
just catch
await catch
try catch with await
(node:1) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): nothing
(node:1) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
(node:1) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): just try catch
(node:1) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 3): just await
```

※エラーにならずにに文字列を出力できているものは、例外を拾うことができている、という認識。以下同様。

`await` 付きで呼び出して `try catch` するか、 `.catch()` するかで例外を拾うことができる。
`.catch()` するときは、`await` の有無は影響しない。

## async function を呼び出す側の例外処理

先ほどの例で、例外を拾えていなかったものが対象。

```js

// 100ms 後に reject される Promise を返す
const asyncFunction = (v) => new Promise((_, r) => setTimeout(() => r(v), 100));

const nothing = async () => {
    asyncFunction('nothing');
};

const justAwait = async () => {
    await asyncFunction('just await');
};

const justTryCatch = async () => {
    try {
        asyncFunction('just try catch');
    } catch (e) {
        console.error(e);
    }
}

//----------------
// just try catch
//----------------
try {
    nothing();
} catch (e) {
    console.error('just try catch: ' + e);
}

try {
    justAwait();
} catch (e) {
    console.error('just try catch: ' + e);
}

try {
    justTryCatch();
} catch (e) {
    console.error('just try catch: ' + e);
}

//------------
// just catch
//------------
nothing().catch(e => console.error('just catch: ' + e));
justAwait().catch(e => console.error('just catch: ' + e));
justTryCatch().catch(e => console.error('just catch: ' + e));


(async () => {
    //----------------------
    // try catch with await
    //----------------------
    try {
        await nothing();
    } catch (e) {
        console.error('try catch with await: ' + e);
    }
    
    try {
        await justAwait();
    } catch (e) {
        console.error('try catch with await: ' + e);
    }
    
    try {
        await justTryCatch();
    } catch (e) {
        console.error('try catch with await: ' + e);
    }

    //-------------
    // await catch
    //-------------
    await nothing().catch(e => console.error('await catch: ' + e));
    await justAwait().catch(e => console.error('await catch: ' + e));
    await justTryCatch().catch(e => console.error('await catch: ' + e));
})();
```

```:実行結果
just catch: just await
try catch with await: just await
(node:1) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): nothing
(node:1) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
(node:1) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): just try catch
(node:1) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 3): nothing
(node:1) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 4): just try catch
(node:1) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 5): nothing
(node:1) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 6): just await
await catch: just await
(node:1) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 7): just try catch
(node:1) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 8): nothing
(node:1) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 9): just try catch
```

次のような場合のみ例外を拾える。

```js
const justAwait = async () => {
    await asyncFunction('just await');
};

// 以下のどれか
justAwait().catch(e => {});

(async () => {
    try {
        await justAwait();
    } catch (e) {
    }

    await justAwait().catch(e => {});
})();
```

## まとめ（再掲）

* async function をただ呼び出すときは `try catch` は意味をなさない
* `await` 付きで呼び出せば `try catch` が意味をなす
* `await` の有無にかかわらず `.catch(e => {})` で拾うことができる