22.4 非同期処理と例外処理

コールバック関数内でエラーをキャッチはできるが、非同期処理の外からは非同期処理の中で例外が発生したかは検知できない。
非同期処理の外から例外が起きたことを知るためには、非同期処理の中で例外が発生したことを非同期処理の外へ伝える方法が必要である。
この非同期処理で発生した例外の扱い方に関して、エラーファーストコールバック、Promise、Async Functionがある。

【22.5 エラーファーストコールバック】
ECMAScript 2015(ES2015)でPromiseが仕様に入るまで、非同期処理中に発生した例外を扱う仕様は存在しなかった。
このため、ES2015より前までは、エラーファーストコールバックという非同期処理中に発生した例外を扱う方法を決めたルールが広く使われていた。
エラーファーストコールバックとは、次のような非同期処理におけるコールバック関数の呼び出し方を決めたルールである。
・処理が失敗した場合は、コールバック関数の1番目の引数にエラーオブジェクトを渡して呼び出す
・処理が成功した場合は、コールバック関数の1番目の引数にはnullを渡し、2番目以降の引数に成功時の結果を渡して呼び出す
しかし、エラーファーストコールバックは非同期処理におけるエラーハンドリングの書き方を決めたコーディングルールにすぎなく、仕様ではない。
そこで、ES2015ではPromiseという非同期処理を扱うビルドインオブジェクトが導入された。

例)

/**
* 100ミリ秒未満のランダムなタイミングでレスポンスを疑似的にデータ取得する関数
* 指定したpathにデータがある場合はcallback(null, レスポンス)を呼ぶ
* 指定したpathにデータがない場合はcallback(エラー)を呼ぶ
*/
function dummyFetch(path, callback) {
  setTimeout(() => {
    // /success からはじまるパスにはリソースがあるという設定
    if (path.startsWith("/success")) {
      callback(null, {body: `Response body of ${path}`});
    } else {
      callback(new Error("NOT FOUND"));
    }
  }, 1000 * Math.random());
}

【22.6 Promise[ES2015]】
PromiseはES2015で導入された非同期処理の結果を表現するビルトインオブジェクトである。
Promiseはビルトインオブジェクトであるため様々なメソッドを持つ。
Promiseでは、非同期処理に成功したときの処理をコールバック関数としてthenメソッドへ渡し、失敗したときの処理を同じくコールバック関数としてcatchメソッドへ渡す。
エラーファーストコールバックとは異なり、非同期処理(asyncPromiseTask関数)はPromiseインスタンスを返す。その返されたPromiseインスタンスに対して、成功と失敗時の処理をそれぞれコールバック関数として渡すという形になる。

asyncPromiseTask().ths(() => {
  // 非同期処理が成功したときの処理
}).catch(() => {
  // 非同期処理が失敗したときの処理
});

Promiseインスタンスのメソッドによって引数に渡せるものが決められているため、非同期処理の流れも一定のやり方に統一される。また非同期処理(asyncPromiseTask関数)はコールバック関数を受け取るのではなく、インスタンスを返すという形に変わる。このPromiseという統一されたインタフェースがあることで、さまざまな非同期処理のパターンを形成できる。

[22.6.1 Promiseインスタンスの作成]
Promiseはnew演算子でPromiseのインスタンスを作成して利用する。このときのコンストラクタにはresolveとrejectの2つの引数をとるexecutorと呼ばれる関数を渡す。executor関数の中で非同期処理を行い、非同期処理が成功した場合はresolve関数を呼び、失敗した場合はreject関数を呼び出す。

const executor = (resolve, reject) => {
  // 非同期の処理が成功したときは引数resolveを実行する
  // 非同期の処理が失敗したときは引数rejectを実行する
};
const onFulfilled = () => {
  // resolveが呼ばれた時の処理
}
const onRejected = () => {
  // rejectが呼ばれた時の処理
}
promise.then(onFulfilled, onRejected);

[22.6.2 Promise#thenとPromise#catch]
このPromiseインスタンスに対してthenメソッドで成功時のコールバック関数だけを登録できるが、thenメソッドで失敗時のコールバック関数だけの登録はできるが、そのときはthen(undefined, onRejected)と第一引数にundefinedを渡す必要がある。
Promise#catchはthen(undefined, onRejected)と同じ意味のため、失敗時の処理だけを登録する場合はcatchメソッドの利用を推奨されている。

[22.6.4 Promiseの状態]
Promiseインスタンスには、内部的に次の3つの状態が存在する。
・Fulfilled:resolveしたときの状態
・Rejected:rejectまたは例外が発生したときの状態
・Pending:FulfilledまたはRejectedではない状態、new Promiseでインスタンスを作成したときの初期状態
しかし、この状態をPromiseのインスタンスから取り出すAPIは存在しない。
Promiseインスタンスの状態は作成時にPendingとなり、一度でもFulfilledまたはRejectedへ変化すると、それ以降状態は変化しなくなる。そのため、FulfilledまたはRejectedの状態であることをSetted(不変)と呼ぶ。

[22.6.9 Promise.allで複数のPromiseをまとめる]
Promise.allメソッドはPromiseインスタンスの配列を受け取り、新しいPromiseインスタンスを返す。
その配列のすべてのPromiseインスタンスがFulfilledとなった場合は、返り値のPromiseインスタンスもFulfilledとなり、一方一つでもRejectedとなった場合は、返り値のPromiseインスタンスもRejectedになる。

[22.6.10 Promise.race]
Promise.allメソッドは複数のPromiseがすべて完了するまで待つ処理である。
Promise.raceメソッドでは複数のPromiseを受け取るが、Promiseが一つでも完了した(Settle状態となった)時点で次の処理を実行する。
この新しいPromiseインスタンスは、配列の中で一番最初にSettle状態となったPromiseインスタンスと同じ状態になる。
・配列の中で一番最初にSettleとなったPromiseがFulfilledの場合は、新しいPromiseインスタンスもFullfilledになる
・配列の中で一番最初にSettleとなったPromiseがRejectedの場合は、新しいPromiseインスタンスもRejectedになる

【22.7 Async Function[ES2017]】
ES2017では、Async Functionという非同期処理を行う関数を定義する構文が導入された。
Async Functionは関数の前にasyncをつけることで定義ができる。
async function doAsync() {
return "値";
}
doAsync().the(value => {
console.log(value); //値
});

[22.7.3 await式]
Async Functionの関数内ではawait式を利用できる。
await式は右辺のPromiseインスタンスがFullfilledまたはRejectedになるまでその場で非同期処理の完了を待つ。
そして、Promiseインスタンスの状態が変わると、次の行の処理を再開する。

JavaScript Primer 迷わないための入門書

JavaScript Primer 迷わないための入門書