【JavaScript】 async/await で非同期処理をわかりやすく記述する
IT技術
async/await という仕組み
本記事では、 JavaScript の非同期処理を扱うための async/await という仕組みについて取り上げます。
async/await は、 Promise を利用した非同期処理をよりわかりやすく記述できます。
本題へ進む前に Promie の復習をしておきましょう!
Promise の復習
async/await は、 Promise の仕組みを利用しているため、Promise への理解が大切だからです。
非同期関数を作成するときに Promise が利用できます。
1return new Promise((resolve, reject) => {});
Promiseの状態
Promise には、以下の状態があります。
apending: 初期状態、実行中。成功も失敗もしていない
fulfilled: 処理が完了した状態。then メソッドを呼び出す
rejected: 処理が失敗した状態。then メソッドと catch メソッドを呼び出す。
- resolve() が呼ばれると、
fulfilled
の状態になります。 - rejected() が呼ばれると、
rejected
の状態になります。
Promise の then
then() は、 Promise のインスタンスの状態が fulfilled
となったときに実行する関数を登録できるインスタンスメソッドです。
then()
は、以下のように定義されています。
1Promise.prototype.then(onFulfilled, onRejected)
onFulfilled
:fulfilled
の状態のとき(resolve が呼ばれたとき)に実行される関数onRejected
:rejected
の状態のとき(reject が呼ばれたとき)に実行される関数
Promise の catch
reject() とは、 Promise のインスタンスの状態が rejected
となったときに実行する関数を登録するインスタンスメソッドです。
1Promise.prototype.catch(onRejected)
onRejected
:rejected
の状態のとき(reject が呼ばれたとき)に実行される関数
Promise の関連記事
async とは
非同期関数を定義する関数定義です。
async function
で非同期関数を定義できます。
async function は Promise インスタンスを返却します。
1async function asyncFunc(/*引数*/) {
2 // 処理
3 return value;
4}
上記の例で定義した asyncFunc
は、以下の関数と同義です。
1// 通常関数で Promise を返却する
2function promiceFunc(/*引数*/) {
3 return new Promise((resolve, reject) => {
4 resolve(value);
5 });
6}
async function は、以下のことを行います
async function
中でreturn
されたとき、戻り値でPromise.resolve
しますasync function
中でPromise
がreturn
されたときは、その返り値のPromise
をそのまま返します。async function
中で例外や値をthrow
したときは、その値でPromise.reject
をします。
async function を実際に使ってみる
async function
を実際に書いてみて、どのように動作するか確認していきましょう。
async function の中で return
async function
で定義された関数内でreturn
されると、その return
の返り値で Promise.resolve
が実行されます。
つまり、 async function
で定義された関数は then() でつなげることができます。
1async function asyncFunc() {
2 // 処理
3 return "resolve";
4}
5
6// then() で return の値を受け取ることができる
7asyncFunc().then(value => console.log(value)); // => resolve
async function の中で Promise を return
async function
で定義された関数内で Promise
を return
すると、そのPromise
がそのまま返却されます。
ですので、then() や catch() で処理を受けることができます。
1async function rejectFunc() {
2 // reject を呼ぶ
3 return Promise.reject(new Error("エラーです"));
4}
5
6rejectFunc().catch(error => console.log(error.message));
7// 実行結果 => エラーです
aysnc function の中で throw
async function
で定義された関数内で throw
された場合、その throw
の値でPromise.reject
が実行されます。
ですので、catch() でエラー処理を行えます。
1// throw
2async function throwFunc() {
3 throw new Error("エラーが発生");
4}
5
6// catch() で例外処理を行える
7throwFunc().catch(error => console.log(error.message)); // => エラーが発生
上記の例で見てもらったらわかるのですが、結局 async function
は Promise
を返すだけの関数です。
外部の関数からaync function
を呼び出すときは、Promise
を返却する非同期関数であると認識していればいいです。
await 式
await 式の使いかた
async function
では、await
式を利用できます。
await
式を利用すると、指定した Promise
の結果が返されるまで処理を待機します。
Promise の状態が、 fulfilled
または rejected
になると、処理を続行します。
1// setTimeout を Promise でラップ
2const wait = ms => new Promise(resolve => setTimeout(() => resolve(), ms));
3
4// async
5async function awaitFunc() {
6 console.log(1);
7 await wait(3000); // Promise が返ってくるまで awaitで 処理停止
8 console.log(2); // 約3秒経過に表示
9}
10
11awaitFunc();
wait() 関数は、setTimeout() を Promise でラップした非同期関数です。
そのため、普通は wait() 関数で処理が止まることはありません。
await
式を利用しなければ、ターミナルにはすぐに「1 2」と表示されます。
ですが、await
式を利用しているため、上記のコードではwait() 関数が処理を終了するまで、次の処理 (console.log(2) ) が評価されません。
ですので、上記のコードを実行してみると、コンソールに 1
が表示されてから、約 3 秒経過した後、2
が表示されます。
await 式の嬉しいところ
await
式を利用することで、非同期処理でありながら、同期処理のように上から順番に処理するように記述できます。
非同期関数でありながら、見通しのよいコードが記述可能となります。
1// 約一秒後、受け取った値を2倍で返す非同期関数
2const promiseFunc = value => {
3 return new Promise((resolve, reject) => {
4 setTimeout(() => {
5 resolve(value * 2);
6 }, 1000);
7 });
8};
9
10// 非同期関数
11async function asyncFunc() {
12 // await 式によって順番に処理される
13 const a = await promiseFunc(1);
14 const b = await promiseFunc(2);
15 const c = await promiseFunc(3);
16
17 return a + b + c;
18}
19
20asyncFunc().then(value => {
21 console.log(value); // => 12
22});
サンプルコード中の関数 promiseFunc() は、処理に約1秒かかります。
asyncFunc()
では await
式で処理が終了するのを待機して、各変数に格納しています。
await
式の利用によって同期処理のような記述になっています。
※ 1つずつ関数の終了を待っているため、このコードは実行に約3秒以上かかります。このままでは問題なので、次の章で効率的に処理できるよう改善します。
async の中で Promise.all()
await
を使うと、非同期処理を同期処理のように記述できわかりやすいです。
ですが、連続で記述された非同期処理を1つずつ待ってしまってはムダな待ち時間が発生します。
その問題を解決するには、 Promise.all() というメソッドを利用します。
Promise.all()
Promise.all() は、引数に非同期関数の配列を受け取り、Promise インスタンスを返すメソッドです。
非同期処理の結果は、配列として戻ってきます。
Promise.all()
に引数として与えられた非同期関数の配列は、並行して実行されます。
そのため、非同期処理を効率的に処理できます。
すべての非同期関数の処理が終わるとPromise.all()
で返ってくる Promise インスタンスの状態は fulfilled
になります。
await 式は返された Promise インスタンスが fulfilled になるまで待機します。
つまり await
式は、Promise インスタンスを返却する処理と組み合わせることができます。
よって await
式は、 Promise.all() メソッドとも組み合わせることができます。
サンプルコード
1const promiseFunc = value => {
2 return new Promise((resolve, reject) => {
3 setTimeout(() => {
4 resolve(value * 2);
5 }, 1000);
6 });
7};
8
9// 非同期関数
10async function asyncFunc() {
11 // 並行して処理が実行され、全ての処理が終わるまで待機
12 const values = await Promise.all([
13 promiseFunc(1),
14 promiseFunc(2),
15 promiseFunc(3)
16 ]);
17
18 console.log(values);
19}
20
21asyncFunc();
[結果]
[ 2, 4, 6 ]
サンプルコード中の関数 promiseFunc() は処理に約1秒かかり、他の変数やデータベースに影響を与えない独立した関数です。
並行して処理を行っても問題がないので、Promise.all() で実行しています。
そして、実行結果を await
式で受け取って、console.log でターミナルに表示しています。
実行してみると、約1秒で処理が終了します。
前の章では同じような処理に3秒かかっていたので、改善できています。
このように、互いに影響を及ぼさない非同期処理は、Promise.all() を利用することで効率的に処理できます。
await 式の中で例外
await
式の中で、 Promise
が rejected
になった場合、その場でエラーを throw
します。
async function
内で発生した例外は自動的にキャッチされます。
何も処理しなければ、async function
が rejected
な Promise
を返します。
サンプルコード
以下サンプルコードです。
1async function asyncFaile() {
2 // await 式で例外発生
3 const value = await Promise.reject(new Error("エラーです"));
4}
5
6// asyncFunc は例外を自動的にキャッチ
7asyncFaile().catch(error => console.log(error.message)); // => エラーです
エラーが throw
されるので try...catch
でエラーをキャッチすることもできます。
1async function asyncFaile() {
2 // await 式で例外発生
3 try {
4 const value = await Promise.reject(new Error("エラーです"));
5 } catch (error) {
6 console.log(error.message); // => エラーです
7 }
8}
9
10// asyncFaile の中で 例外処理がされているので、catch()は呼ばれない
11asyncFaile()
12 .then(() => console.log("呼ばれる")) // => 呼ばれる
13 .catch(error => console.log("呼ばれない"));
async function
内ではエラーを throw
していないので、fulfilled
な Promise
が返されることになり、asyncFaile().then
は呼ばれますが、asyncFaile().catch
は呼ばれません。
もし、上位の関数でエラー処理したい場合はcatch
したのち、再び throw
してください。
1async function asyncFaile() {
2 // await 式で例外発生
3 try {
4 const value = await Promise.reject(new Error("エラーです"));
5 } catch (error) {
6 throw error;
7 }
8}
9
10// asyncFaile で エラーが throw されるので、catch で受ける。
11asyncFaile()
12 .then(() => console.log("正常処理")) // 呼ばれない
13 .catch(error => console.log(error.message)); // => エラーです
async/await の対応状況
Chrome、Firefox、Edge、Safari といった最新ブラウザは対応済みです。
IE11 は未対応です。
ECMAScript 2016+ compatibility table
もし、IE もサポートに含めるのなら、Babel でトランスパイルする必要があります。
さいごに
以上、async/await についてでした。
それでは、最後に、今回の内容をまとめてみます!
- async/await 式で非同期処理を扱える
- async function で定義された関数は、Promise インスタンスを返す
- await 式は、Promise インスタンスが fulfilled の状態になるまで、次の処理を評価しない
- await 式は、Promise.all のような Promise インスタンスを返す処理と組み合わせることができる
非同期処理の async/await についてでした。
本記事が JavaScript のコード作成のお役にたてたら幸いです。
こちらの記事もオススメ!
2020.08.07JavaScript 特集知識編JavaScriptを使ってできることをわかりやすく解説!JavaScriptの歴史【紆余曲折を経たプログラミン...
2020.07.17ライトコード的「やってみた!」シリーズ「やってみた!」を集めました!(株)ライトコードが今まで作ってきた「やってみた!」記事を集めてみました!※作成日が新し...
ライトコードでは、エンジニアを積極採用中!
ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。
採用情報へ
「好きを仕事にするエンジニア集団」の(株)ライトコードです! ライトコードは、福岡、東京、大阪、名古屋の4拠点で事業展開するIT企業です。 現在は、国内を代表する大手IT企業を取引先にもち、ITシステムの受託事業が中心。 いずれも直取引で、月間PV数1億を超えるWebサービスのシステム開発・運営、インフラの構築・運用に携わっています。 システム開発依頼・お見積もり大歓迎! また、現在「WEBエンジニア」「モバイルエンジニア」「営業」「WEBデザイナー」を積極採用中です! インターンや新卒採用も行っております。 以下よりご応募をお待ちしております! https://rightcode.co.jp/recruit