今さらだけどawait
awaitは2017年あたりに出てきた機能で新しくもないですが、私は新しい機能が出ても大体の環境にサポートされるまで触らない意識低い系エンジニアなので、IEで使えない async/awaitとPromiseは存在を知りつつもスルーしてきたのですが、そろそろIEを切ってもいいと思えたので、解禁してみました。
本記事の内容
この記事はawaitがどんな時に役立つかを紹介するものです。
Promiseのことは知っている前提の記事なので、Promiseの使い方を知らない人は、まずそちらを調べてから読むのがおすすめです。
awaitの使い方
awaitの使い方について簡単におさらいです。
awaitはasyncをつけた非同期関数の中で使え、Promiseを返す関数呼び出しの前に書きます。
async function main() {
await asyncFunction();
}
本記事でawaitを使っているコードは、明示していなくても全て非同期関数内の前提です。
await は何ができるのか?
awaitは非同期処理が終わるまで待つ機能で、awaitをつけると、Promiseの処理が終わるまで待って、終わった後に次の行に進みます。
そして、Promiseがresolveする値を、同期関数の戻り値のように扱うことができます。
例として、fetch関数でリソースを取得して、レスポンスのHTTPステータスを出力するコードを考えると、awaitなしでは以下のようになります。
fetch(resource).then(response => {
console.log(response.status);
});
これが、awaitを使うと以下のように書けます。
const response = await fetch(resource);
console.log(response.status);
もう一つのポイントとしては、awaitの待機はUIスレッドをブロックせず、CPUリソースを無駄に食うこともありません(たぶん)
await の使いどころ
ここから具体的にawaitがどういうところで役立つか、例を挙げていきたいと思います。
非同期処理を順番に実行する
おそらく一番基本的で分かりやすいawaitの使い道だと思います。
Promiseを返す関数 a、b、cがあるとして、これを順番に実行する場合、awaitを使わないと以下のようになります。
a().then(() => {
return b();
}).then(() => {
return c();
}).then(() => {
console.log('end');
})
ごちゃっとしていて、処理の流れがぱっと見で分かりづらいですね。
このコードが、awaitを使うと以下のようにとても見やすくなります。
await a();
await b();
await c();
console.log('end');
非同期処理の結果を順番に使う
上の例の派生系ですが、awaitを使うと非同期処理の実行結果も、同期処理的のような書き方で使えるようになります。
const resultA = await a();
const resultB = await b(resultA);
const resultC = await c(resultB);
console.log(resultC);
一定時間処理を止めるsleep関数
例えば、開始から10秒後にログ出力したい場合、awaitなしでは以下のようにsetTimeoutなどを使って、待機後の処理を別関数に切り出す必要があります。
console.log('START');
setTimeout(() => {
console.log('10秒経過');
}, 10 * 1000);
awaitを使えば、以下のようなsleep関数を実現することができます。
console.log('START');
await sleep(10 * 1000);
console.log('10秒経過');
function sleep(milliSeconds) {
return new Promise((resolve) => {
setTimeout(() => resolve(), milliSeconds);
});
}
通信エラーが起きたら1分待って再通信する
先に書いたsleep関数を使うと、通信が成功するまで1分間隔でリトライし続けるというコードがこんなにシンプルに書けます。
while (true) {
try {
return await fetch(resource);
} catch (e) {
await sleep(60);
}
}
このように、awaitを使うと、非同期処理をwhileやforやifなどの制御文で制御できるようになります。
ユーザーの入力を待つ
await はユーザーの入力も待つことができます。
この使い方を思いついたときは、自分天才か?!と思いましたが、ググってみると普通にネットにいっぱい例があります(笑)
awaitで一番便利に感じたところで、この記事を書くモチベーションになりました。
例えば、ダイアログでYESかNOを選んで処理を分岐させたい場合、awaitなしならコールバック関数などを使う必要があります。
やり方は色々あると思いますが、例えばこんな感じです。
function main() {
const yes = () => {
// yesの処理
};
const no = () => {
// noの処理
};
selectYesNo({yes, no});
}
function selectYesNo(callbacks) {
yesButton.onclick = () => callbacks.yes();
noButton.onclick = () => callbacks.no();
// ダイアログ表示処理(省略)
}
これが、awaitを使うことによりYES/NOダイアログの結果を得るまで待ってif 文で判定するという、非常に分かりやすいコードにできます。
async function main() {
const answer = await selectYesNo();
if (result === 'yes') {
// yesの処理
} else if (result === 'no') {
// noの処理
}
}
function selectYesNo() {
// ダイアログ表示処理(省略)
return new Promise(resolve => {
yesButton.onclick = () => resolve('yes');
noButton.onclick = () => resolve('no');
});
}
コールバック関数などを組み合わせて実装する必要があった、ユーザー入力を挟む一連の処理を、awaitを使えばで、一つの関数スコープで順序立てて書くことができるようになるります。
awaitでユーザー入力を待つと、簡単かつ綺麗にロジックとUIの分離ができる
awaitでユーザー入力を待つと、ロジックとUIを簡単かつ綺麗に分離することができます。
async function logic(selectYesNo) {
const answer = await selectYesNo();
if (answer === 'yes') {
// yesの処理
} else if (answer === 'no') {
// noの処理
}
}
ここで引数の selectYesNo はユーザーにYesかNoかを選ばせる非同期関数を想定しています。
このような非同期関数を引数に取ると、logic関数はどのようなUIでユーザーが選ぶのか知る必要がなく、その結果だけを使って処理を進めることができます。
selectYesNoがダイアログ入力だろうが、コマンド入力だろうが、どんなUIだろうがlogic関数側には関係がなくなります。
await のメリットとは
まとめると、awaitのメリットはこんな感じでしょうか。
- 非同期処理を実行順に書ける
- 非同期処理の結果が同期処理のように使える
- 非同期処理をif、while、forなどの制御文で制御できる
- なんでもいつまでも待てる。ユーザーの入力も待てる
awaitを使うと、コールバック関数などを組み合わせて書く必要があった複雑な処理を、シンプルに書けるようになります。