async / await
| 対応: | ES2017(ECMAScript 2017) |
|---|
非同期処理を同期処理のように直感的に記述するための構文です。『async』で非同期関数を宣言し、『await』でPromiseの完了を待ちます。
構文
// async関数を宣言する。戻り値は自動的にPromiseになる
async function 関数名() {
// awaitでPromiseの完了を待つ
var result = await Promise;
}
// 関数式での書き方
var 関数名 = async function() {
var result = await Promise;
};
キーワード一覧
| キーワード | 概要 |
|---|---|
| async | 関数の前に付けることで、その関数を非同期関数にします。非同期関数の戻り値は常にPromiseになります。 |
| await | Promiseの完了を待ち、結果の値を取り出します。async関数の中でのみ使用できます。 |
サンプルコード
以下のサンプルでは動作確認用のヘルパー関数を使用しています。Promiseの基本については『promise』を先に参照してください。
// テスト用の非同期関数
function wait(ms) {
return new Promise(function(resolve) {
setTimeout(function() {
resolve("" + ms + "ミリ秒待ちました");
}, ms);
});
}
// async/awaitの基本的な使い方
async function main() {
console.log("開始");
var result = await wait(2000); // 2秒間待つ
console.log(result); // 『2000ミリ秒待ちました』と出力されます。
console.log("完了");
}
main();
// then()を使った書き方との比較
// then()版
wait(1000).then(function(msg1) {
console.log(msg1);
return wait(1000);
}).then(function(msg2) {
console.log(msg2);
});
// async/await版
async function sequential() {
var msg1 = await wait(1000);
console.log(msg1); // 『1000ミリ秒待ちました』と出力されます。
var msg2 = await wait(1000);
console.log(msg2); // さらに1秒後に『1000ミリ秒待ちました』と出力されます。
}
sequential();
// try...catchでエラーを処理する
async function fetchUserData() {
try {
var response = await fetch("https://wp-p.info/sandbox/api.php");
var data = await response.json();
console.log(data.name);
} catch (error) {
console.log("データの取得に失敗しました: " + error.message);
} finally {
console.log("通信処理が完了しました");
}
}
fetchUserData();
// 複数の非同期処理を並列実行する方法
async function loadAll() {
// Promise.all()とawaitを組み合わせて、並列実行の完了を待つ
var results = await Promise.all([
fetch("https://wp-p.info/sandbox/api.php"),
fetch("https://wp-p.info/sandbox/api.php")
]);
console.log("すべてのデータを取得しました");
}
loadAll();
※ fetch()を使ったサンプルはブラウザの開発者ツール(コンソール)で実行してください。
開始 2000ミリ秒待ちました 完了 1000ミリ秒待ちました 1000ミリ秒待ちました 1000ミリ秒待ちました
async関数の戻り値
async関数は常にPromiseを返します。関数内で『return』した値は自動的に『Promise.resolve()』で包まれるため、呼び出し元では『await』や『then()』で受け取れます。
// async関数のreturnはPromise.resolve()で包まれる
async function getGreeting() {
return "こんにちは、八神庵!";
}
// 戻り値はPromiseなので、awaitで取り出す
async function show() {
var message = await getGreeting();
console.log(message); // 『こんにちは、八神庵!』と出力されます。
}
show();
// then()でも受け取れる
getGreeting().then(function(msg) {
console.log(msg); // 『こんにちは、八神庵!』と出力されます。
});
// 明示的にreturnしない場合はundefinedのPromiseが返る
async function noReturn() {
console.log("処理実行");
}
noReturn().then(function(result) {
console.log(result); // 『undefined』と出力されます。
});
ループの中でのawait
for文の中で『await』を使うと、非同期処理を1つずつ順番に実行できます。ただし処理が直列になるため時間がかかります。並列実行したい場合は『Promise.all()』を使ってください。
// 直列実行: 1つずつ順番に処理する(合計3秒)
async function serial() {
var urls = ["/api/users", "/api/posts", "/api/comments"];
var i;
for (i = 0; i < urls.length; i++) {
var response = await fetch(urls[i]);
console.log(urls[i] + " を取得しました");
}
}
// 並列実行: すべてを同時に開始する(最も遅いもの1つ分の時間)
async function parallel() {
var urls = ["/api/users", "/api/posts", "/api/comments"];
var promises = urls.map(function(url) {
return fetch(url);
});
var responses = await Promise.all(promises);
console.log(responses.length + " 件取得しました");
}
配列の『forEach()』の中で『await』を使っても待機されません(forEachはasync関数ではないため)。ループで順番にawaitしたい場合は必ず通常の『for文』を使ってください。
NG: forEach の中の await は待機されません。
async function wrong() {
var ids = [1, 2, 3];
ids.forEach(async function(id) {
var data = await fetch("/api/users/" + id);
console.log(id); // 順番が保証されません
});
console.log("完了"); // forEach の中の await を待たずに実行されます
}
OK: for 文なら順番に待機されます。
async function correct() {
var ids = [1, 2, 3];
var i;
for (i = 0; i < ids.length; i++) {
var data = await fetch("/api/users/" + ids[i]);
console.log(ids[i]); // 1, 2, 3 の順番で出力されます
}
console.log("完了"); // すべて終わった後に実行されます
}
よくあるミス1: awaitの付け忘れ
await を付け忘れると、Promise オブジェクトがそのまま変数に入ります。
async function mistake1() {
var data = fetch("/api/users"); // await がないので Promise オブジェクトが入ります
console.log(data); // 『Promise { <pending> }』と出力されます
}
正しい書き方: await を付けてレスポンスを受け取ります。
async function correct1() {
var response = await fetch("/api/users");
var data = await response.json();
console.log(data); // レスポンスの JSON データが出力されます
}
よくあるミス2: asyncの付け忘れ
async を付け忘れて await を使うと SyntaxError になります。
// async なし関数で await を使うと SyntaxError になります
function mistake2() {
// var result = await fetch("/api"); // SyntaxError
}
// ミス3: catch し忘れると Unhandled Promise Rejection になります
async function mistake3() {
var response = await fetch("https://invalid-url.example.com");
// ネットワークエラーが発生しますが、catch していないため
// コンソールに Unhandled Promise Rejection の警告が表示されます
}
// mistake3(); // 必ず try...catch で囲むか、.catch() を付けてください
実践パターン: ローディング表示
Webアプリケーションでは、データの取得中にローディング表示を出し、完了後に非表示にするパターンが頻出します。
<button id="loadBtn">データを取得</button> <p id="loading" style="display: none;">読み込み中...</p> <div id="result"></div>
var loadBtn = document.getElementById("loadBtn");
var loading = document.getElementById("loading");
var result = document.getElementById("result");
if (loadBtn && loading && result) {
loadBtn.addEventListener("click", async function() {
// ローディング表示を出す
loading.style.display = "block";
loadBtn.disabled = true;
try {
var response = await fetch("https://wp-p.info/sandbox/api.php");
var data = await response.json();
result.textContent = "取得完了: " + JSON.stringify(data);
} catch (error) {
result.textContent = "エラー: " + error.message;
} finally {
// ローディング表示を消す(成功・失敗に関わらず)
loading.style.display = "none";
loadBtn.disabled = false;
}
});
}
『finally』ブロックを使うことで、成功時も失敗時も必ずローディング表示を消す処理が実行されます。ボタンの『disabled』も連動させることで、二重送信を防止できます。
awaitの注意点
『await』はasync関数の中でしか使えません。通常の関数やグローバルスコープで使用するとエラーになります。
// エラーになる書き方
function normal() {
var result = await wait(1000); // SyntaxError: async関数の外では使えません。
}
// async関数の中で使う正しい書き方
async function correct() {
var result = await wait(1000); // 正常に動作する
console.log(result);
}
correct();
また、awaitを順番に書くと処理が直列に実行されるため、互いに依存しない処理は『Promise.all()』で並列実行した方が効率的です。
// 直列実行: 合計3秒かかる
async function slow() {
var a = await wait(1000); // 1秒待つ
var b = await wait(2000); // さらに2秒待つ
console.log(a, b);
}
// 並列実行: 最も遅い2秒で完了する
async function fast() {
var results = await Promise.all([wait(1000), wait(2000)]);
console.log(results[0], results[1]);
}
概要
『async / await』はES2017で追加された、Promiseをより簡潔に扱うための構文です。『Promise』の『then()』チェーンでは処理が複雑になるほどコードが読みにくくなりますが、『async / await』を使うと通常の同期処理と同じ見た目で非同期処理を記述できます。
async関数は必ずPromiseを返します。関数内で値を『return』すると、それは自動的に『Promise.resolve()』で包まれます。エラーが発生した場合は『Promise.reject()』で包まれ、呼び出し元で『catch()』や『try...catch』で処理できます。
非同期処理には『async / await』がよく使われています。サーバーとの通信、データベースへの問い合わせ、ファイルの読み書きなど、完了を待つ必要がある処理を直感的に書ける構文です。
対応ブラウザ
51 以前 ×
Android Browser
60 以降 ○
54 以前 ×
Chrome Android
60 以降 ○
54 以前 ×
Firefox Android
79 以降 ○
51 以前 ×記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。