こんにちは、フロントエンドエンジニアのまさにょんです。
今回は、JavaScriptでasync/await で非同期処理 (async/await使ってPromiseを簡単にする)方法について解説します。
目次
async/await で非同期処理(Promise)をより簡単にする
JavaScriptの非同期処理には、欠かせない Object である Promise
そんな Promiseの記述をより簡単に使いやすくするためにできたのが、async/await です。
今回は、そんな async/await の使い方やポイントについてまとめています。
Promise については、以前の記事でまとめていますので、よかったら参照してみてください。
asyncとは、非同期関数 (async関数)を定義するためのKeyWord
async とは、非同期関数 (async関数)を定義するためのKeyWordです。
ポイントは、async を関数の宣言の前に付けると、その関数は実行結果(返り値)として必ず Promise を返すようになることです。
MDNの説明がわかりやすく、詳しいので引用します。
[ 非同期関数 ]
非同期関数は
async
キーワードで宣言され、その中でawait
キーワードを使うことができます。
async
およびawait
キーワードを使用することで、プロミスベースの非同期の動作を、プロミスチェーンを明示的に構成する必要なく、
よりすっきりとした方法で書くことができます。
[ 返り値 ]
Promise
で、非同期関数から返される値で解決するか、または非同期関数内で捕捉されなかった例外で拒否されます。
[ 解説 ]
非同期関数には、
await
式を置くことができます。await 式は返されたプロミスが履行されるか拒否されるまで実行を中断することで、
プロミスを返す関数をあたかも同期しているかのように動作させます。
プロミスの解決済みの値は、await 式の返値として扱われます。
async
とawait
を使用すると、非同期コードに通常のtry
/catch
ブロックを使用することができます。メモ:
async
/await
の目的は、プロミスベースの API を利用するのに必要な構文を簡素化することです。非同期関数は常にプロミスを返します。
非同期関数の返値が明示的にプロミスでない場合は、暗黙的にプロミスでラップされます。
引用元: MDN: 非同期関数
上記の説明にもあるとおり、
async
文と、この後説明するawait
文の目的は、Promise ベースの API を利用するのに必要な構文をより簡単に記述できるようにすることです。
非同期関数 (async関数)は、実行結果(返り値)として常に Promise を返します。
また、非同期関数 (async関数)の返り値(returnの値)が明示的に Promise でない場合は、暗黙的に Promise でラップされます。
つまり、return new Promise((resolve, reject) => { ・・・} )
ではなく、
return '非同期-ロボ玉';
のように、Promiseではない、普通の返り値を設定した場合は、
その返り値をPromise でラップしてくれるのです。
このような Promiseで自動的にラップしてくれるような機能のおかげで、
Promiseを返すために、いちいち「 new Promise() 」する必要がなくなりCodeがより簡潔になります。
// 1. async で「非同期関数」を定義する => 必ずPromiseを返します。
const AsyncRobotama = async () => {
return 'AsyncRobotama';
}
// 2. return new Promise((resolve, reject) => { ・・・} ) を省略できる!
// 3. 非同期関数 (async関数)の実行結果(返り値)は Promise なので、thenメソッドが使える!
AsyncRobotama()
.then( result => {
console.log(result);
});
// [ 実行結果 ]
// AsyncRobotama
// Promise {<fulfilled>: undefined}
// [[Prototype]]: Promise
// [[PromiseState]] : "fulfilled"
// [[PromiseResult]]: undefined
上記のSampleCodeでは、明示的に Promise を return
していませんが、自動で Promise にラップされた返り値になります。
もちろん、async関数で今まで通りの方法で Promise を実行結果(返り値)として返すこともできます。
// もちろん、async関数で今まで通りの方法でPromiseを返すこともできます。
const AsyncRobotama2 = async () => {
return new Promise((resolve, reject) => {
resolve('AsyncRobotama2');
});
}
AsyncRobotama2().then(result => {
console.log(result);
});
// [ 実行結果 ]
// AsyncRobotama2
// Promise {<fulfilled>: undefined}
// [[Prototype]]: Promise
// [[PromiseState]] : "fulfilled"
// [[PromiseResult]]: undefined
awaitとは、async関数からPromiseが返ってくるのを待つKeyWord
awaitとは、async関数からPromiseが返ってくるのを待つためのKeyWordであり、非同期処理の実行を同期的に扱うための機能です。
awaitは、Promiseオブジェクトを返す関数の実行中に使用され、Promiseが解決(resolve)されるまで、その関数の実行を一時停止し、Promiseが決定されるのを待ちます。
await を使用する際のポイントは、await は必ず、async関数の内部で使用することです。
async関数内でawaitを使うことで、非同期処理を同期処理のように扱えます。
async関数の中だけで省略Keywordであるawaitは使えるので「async/await」はセットで考えられるものなのです。
// 1. async で「非同期関数」を定義する => 必ずPromiseを返します。
const AsyncRobotama3 = async () => {
return 'AsyncRobotama3';
}
const DevlopRobotama = async () => {
// 2. await は必ず、async関数の内部で使います!
const result = await AsyncRobotama3();
console.log(result);
}
DevlopRobotama();
// [ 実行結果 ]
// AsyncRobotama3
// Promise {<fulfilled>: undefined}
// [[Prototype]]: Promise
// [[PromiseState]] : "fulfilled"
// [[PromiseResult]]: undefined
こちらもMDNの説明がわかりやすく、詳しいので引用します。
[ await ]
await
演算子はプロミス (Promise
) を待つために使用します。通常の JavaScript コードで、
async function
の内部でのみ使用することができます。
async function
によってPromise
が返されるのを待機するために使用します。[ 解説 ]
await
式はasync
関数の実行を一時停止し、
Promise
が決定される(すなわち履行または拒否される)まで待ち、履行された後に
async
関数の実行を再開します。最下位時に、
await
式の値は履行されたPromise
の値になります。
Promise
が拒否された場合、await
式は拒否された値で例外を発生させます。
await
演算子に続く式の値がPromise
ではなかった場合、解決された Promise に変換されます。[ Promiseの履行を待つ ]
Promise
がawait
式に渡された場合、Promise
が履行されて履行値を返すのを待ちます。[ Promiseへの変換 ]
値が
Promise
でない場合は、値を解決済みのPromise
に変換して待ちます。[ Promiseの拒否 ]
Promise
が拒否された場合、拒否された値で例外が発生します。[ 拒否されたPromiseの処理 ]
拒否された
引用元: MDN: awaitPromise
はtry
ブロックなしで処理することができます。
上記にあるように、awaitは、async function
によってPromise
が返されるのを待機するために使用します。
Promise が履行か拒否かのどちらかの状態 (PromiseStatus)になったとき(Promiseの返却時)に、async関数の実行を再開します。
awaitはthen()
を続けて値を取り出す Promiseチェーンを省略した記法です。
つまり、Promise.then( result => { ... } )
を省略したものということです。
次のSampleCodeを見ると、await の効果がわかりやすいと思います。
// 1. 非同期処理をする関数
const PromiseCalc = (num) => {
return new Promise((resolve) => {
setTimeout(()=> { resolve(num * num) }, 2000);
});
};
// 2. 従来のthen() メソッドチェーン
PromiseCalc(10)
.then((data)=> {
console.log(data);
return PromiseCalc(100);
})
.then((data)=> {
console.log(data);
return PromiseCalc(1000);
})
.then((data)=> {
console.log(data);
});
// 3. async/await による thenメソッドチェーンの省略
const AsyncAll = async () => {
console.log(await PromiseCalc(10));
console.log(await PromiseCalc(100));
console.log(await PromiseCalc(1000));
}
AsyncAll();
// [ 実行結果 ]
// Promise {<fulfilled>: undefined}
// [[Prototype]]: Promise
// [[PromiseState]] : "fulfilled"
// [[PromiseResult]]: undefined
// 100
// 100
// 10000
// 10000
// 1000000
// 1000000
複数のPromiseを並列で走らせるときに、awaitは使いやすいです。
await を使わないと then()
でデータを取り出してネストが深くなり、処理が複雑になりがちです。
// 複数のPromiseを並列で走らせるときに、awaitは使いやすい
// 1. 非同期処理をする関数
const LateSeconds = (x) => {
return new Promise(resolve => {
setTimeout(() => {
resolve(x);
}, x * 1000);
});
}
// 2-1. async関数 Ver.1
const LateCompany = async () => {
let a = LateSeconds(1); // ここで1秒待つ
let b = LateSeconds(2); // ここで2秒待つ
return `${await a + await b}秒会社に遅刻しました!`; // 1秒待つのと2秒待つのが並行で行われる
}
// 2-2. then() でも取り出せます
LateCompany().then(v => {
console.log(v);
});
// [ 実行結果 ]
// 3秒会社に遅刻しました!
// 3-1. async関数 Ver.2
const LateCompany2 = async (x) => {
let a = await LateSeconds(2); // ここで2秒待つ
let b = await LateSeconds(3); // さらに3秒待つ
return `${await a + await b}秒会社に遅刻しました!`; // 2秒待つのと3秒待つのが並行で行われる
}
// 3-2. await でも取り出せます
console.log(await LateCompany2());
// [ 実行結果 ]
// 5秒会社に遅刻しました!
Promise.all() と async/await を使った並列処理の比較
Promise.all() と async/await を使った並列処理の比較をしてみましょう。
// 1. 非同期処理をする関数
const PromiseCalc = (num) => {
return new Promise((resolve) => {
setTimeout(()=> { resolve(num * num) }, 2000);
});
};
// [ Promise.all() と async/await を使った並列処理の比較 ]
// 2. Promise.all() を使った並列処理
// 2-1. Promise.all() では、PromiseCalc() の引数違いを3つ同時に実行しています。
Promise.all([
PromiseCalc(10),
PromiseCalc(100),
PromiseCalc(1000)
])
.then((data) => {
// 2-2. 最後に「then」で実行結果を配列として出力します。
console.log(data);
});
// [ 実行結果 ]
// (3) [100, 10000, 1000000]
// 3. async/await を使った並列処理
const AsyncAllFunc = async () => {
// 3-1. まず最初に、実行予定のPromise処理をすべて起動させて変数に格納します。
const promise1 = PromiseCalc(10);
const promise2 = PromiseCalc(100);
const promise3 = PromiseCalc(1000);
// 3-2.「await」を付与することですべてのPromise処理を並列に動かして結果を取得できる。
console.log([await promise1, await promise2, await promise3 ]);
};
AsyncAllFunc();
// [ 実行結果 ]
// (3) [100, 10000, 1000000]
関連記事
JavaScript書籍 Ver. 中級-上級者向け
JavaScript書籍 Ver. 初級者向け
Twitterやってます!Follow Me!
神聖グンマー帝国の逆襲🔥
神聖グンマー帝国の科学は、世界一ぃぃぃぃぃぃ!!!!!