非同期処理(Promise, async/await)
ポーリング
フロント側からサーバーに対して一定間隔にhttpリクエストを投げる処理。 サーバから新しい情報を取得する目的で利用する。
Promsie
非同期処理を行う最も代表的なオブジェクト。 PromiseにはPromiseStatusという状態があって3種類ある。
- pending - 未解決(処理完了まで待機中)
- resolved - 解決済み(処理が無事に終わった)
- rejected - 拒否(処理が失敗に終わった)
New Promise()
でPromiseオブジェクトが生成されると、PromiseStatusはpending
(待機中)になる。
処理が成功したらPromiseStatusはresolved
(解決済み)に変わり、then
に書かれた処理が実行される。
失敗したらPromiseStatusはrejected
(失敗)に変わり、catch
に書かれた処理が実行される。
Promiseの書き方
const promise = new Promise((resolve, reject) => {})
引数には関数を渡し、その関数の第一引数にresolveを設定し、第二引数にrejectを任意で設定する。resolve、rejectともに関数である。
resolve
resolveさせてみると、
// rejectは今回使わない const promise = new Promise((resolve) => { resolve(); }).then(() => { console.log("resolve done!"); });
この時、Promiseオブジェクトが生成される=>resolveメソッド呼び出しで状態がresolvedに変わる=>resolved(解決済み)なのでthenの処理が実行される("resolve done!"が表示される)、という流れで処理が行われる。
また、resolveメソッドは引数を受け取ることもできる。
const promise = new Promise((resolve) => { resolve("parameter is passed!!"); }).then((value) => { console.log(value); });
実行すると、「"parameter is passed!!"」が表示される。resolveメソッドに渡した「"parameter is passed!!"」がthenの第一引数valueに渡されていることがわかる。resolveメソッドに渡した「"parameter is passed!!"」はPromiseValue
というものに格納されており、次に呼ばれるメソッドの第一引数に渡すことができる。
reject
次にrejectすると、
const promise = new Promise((resolve, reject) => { reject(); }).then(() => { console.log("resolve done!"); }).catch(() => { console.log("reject done!"); });
thenの処理は実行されることなく、catchの処理が実行されて「"reject done!"」と出る。
この時、Promiseオブジェクトが生成される=>rejectメソッド呼び出しで状態がrejectedに変わる=>catchに飛ぶ=>"rejected done!"が表示される、という流れで処理が行われる。
ちなみにcatchで処理実行された後、「処理が正常に実行された」ということで状態はresolveになっている。注意。
Promise.all
Promise.all()
は配列でPromiseオブジェクトを渡し、全てのPromiseオブジェクトがresolvedになったら、次の処理に進む。
const promise1 = new Promise((resolve) => { setTimeout(() => { resolve(); }, 1000); }).then(() => { console.log("promise1 done!!"); }); const promise2 = new Promise((resolve) => { setTimeout(() => { resolve(); }, 1000); }).then(() => { console.log("promise2 done!!"); }); Promise.all([promise1, promise2]).then(() => { console.log("All promises done!!"); }).catch((err) => { console.log(err); });;
実行結果は以下の通り。
promise1 done!! promise2 done!! All promises done!!
Promise.allは配列を一行ごとにPromiseで処理しなければならない時に有効である。foreachで実装しようとしても、エラーが出てしまう。foreachはコールバックの返り値を無視するからである。そこで代わりにPromise.allを使う。1つでも処理が失敗すれば、全てが失敗となる。
Promise.race
Promise.race()
は配列でPromiseオブジェクトを渡し、どれか1つのPromiseオブジェクトがresolvedになったら、次の処理に進む。
const promise1 = new Promise((resolve) => { setTimeout(() => { resolve(); }, 1000); }).then(() => { console.log("promise1 done!!"); }); const promise2 = new Promise((resolve) => { setTimeout(() => { resolve(); }, 3000); }).then(() => { console.log("promise2 done!!"); }); Promise.race([promise1, promise2]).then(() => { console.log("One promise done!!"); });
実行結果は以下の通り。
promise1 done!! One promise done!! promsie2 done!!
async/await
Promiseは便利だが、インデントが深くなりがち。1つの非同期処理を行うメソッドには不向き。そこで、await/asyncによって同期処理のように書くことができる。
async
asyncは非同期関数を定義する関数宣言であり、関数につけることでPromiseオブジェクトを返す関数になる。
const printLog = async () => { return 1 };
printLog()が値を返した時、Promiseは戻り値をresolveし、戻り値はPromiseValueとして扱われる。
await
awaitはPromiseオブジェクトが値を返すのを待っている演算子。必ずasyncなメソッドの中で使うこと。
例
const asyncFunc = async () => { let x, y; x = new Promise(resolve => { setTimeout(() => { resolve(1); }, 1000 ) }).then((val) => { console.log(val); }); y = new Promise(resolve => { setTimeout(() => { resolve(1); }, 1000 ) }).then((val) => { console.log(val); }); console.log(x + y) }
これはawaitを使うと、よりシンプルになる。
const asyncFunc = async () => { let x, y; x = await new Promise(resolve => { setTimeout(() => { resolve(1); }, 1000 ) }); y = await new Promise(resolve => { setTimeout(() => { resolve(1); }, 1000 ) }); console.log(x + y) }
実行結果
2
awaitがあることで、resolved(解決)した後に実行されるthenがなくなった。
以上のことから、Promiseオブジェクトを受け取りにはawaitが必要である。awaitが必要ということは、その処理はasyncなメソッドの中にある必要があるということを認識しておくこと。