await
って何?
await
はプロミスの履行を待つ構文です。
これを使うと、Promise
のthen
をよりすっきりとしたコードに置き換えることができます。
await
を使わずにプロミスの結果を使いたいとき、通常はthen
メソッドを使います。
順番に実行した非同期処理が複数あるなら、then
をチェインしていました。
fetch('https://example.com') // HTTPリクエストを送信
.then(res => res.text()) // レスポンスボディを取得
.then(text => console.log(text)) // 出力
await
を使うと、このコードを以下のように書くことができます。
// Responseオブジェクトが入る
const response = await fetch('https://example.com')
// レスポンスボディを取得する
const body = await response.text()
console.log(body) // 出力する
詳細
await
は式の左に置くことができます。
また、await
の右にPromise
オブジェクトを置くことで、そのプロミスの履行結果を取得できます。
const promise = // Promiseオブジェクト
const result = await promise
結果の取得に時間がかかる場合、await
以降の処理はその時間が経ってプロミスが履行されてから実行されます。
そのためawait
を書いた部分の実行には時間がかかることがあります。
なお、このようにプロミスを直接置くより、プロミスを返す関数を書くことが多いです。
const result = await プロミスを返す関数()
async
って何?
async
は関数の前につけることができるキーワードです。
function
の前につけたり、アロー関数の前につけることもできます。
// 関数宣言の前にasyncをつける
async function name() {
// 処理
}
そして、await
はasync
がついた関数の中でしか使えません。
例えば、次のコードは構文エラーとなります。
// asyncがない関数
function fetchExample() {
return await fetch('example.com')
}
このエラーを修正するには、関数の前にasync
をつける必要があります。
// 先頭にasyncをつける
async function fetchExample() {
return await fetch('example.com')
}
コードのトップレベルならTop-Level Awaitという仕様があるので、例外的に使えます。
戻り値
async
がついている関数は戻り値がプロミスになります。
async function example() {
console.log('Hello World!')
}
example() // Promise
// Hello World! <- console.logは実行される
このプロミスは関数内の処理が終わったとき(最後のawait
が履行されたとき)に履行されます。
async function fetchExample() {
const res = await fetch('example.com')
const text = await res.text() // textはstringになる
return text
}
fetchExample() // Promiseが返ってくる
await fetchExample() // string
もしasync
をつけた関数内に一つもawait
がない、かつ関数がプロミスを返さない場合、このプロミスは即座に履行されます。
わざわざasync
をつける理由として、この挙動が挙げられると思います。
もしasync
がなかったとして、await
をつけた処理があるだけで関数の戻り値が勝手にプロミスになるのは問題です。
そのため明示的にasync
をつけ、この中では非同期処理が実行されているよ、この関数はプロミスを返すよ、というのが一目でわかるようにします。
おまけ
エラーハンドリング
プロミスでの処理内で発生したエラーは、通常のエラーとは違ってtry-catch
で捕捉できません。
その代わりプロミスのcatch
メソッドで捕捉できます。
function getPromise() {
// 処理内でエラーが発生するプロミス
return new Promise(() => {
throw new Error('Error!')
})
}
// try-catchの場合
try {
getPromise()
} catch (e) {
console.log('エラーをキャッチ')
}
// Uncaught (in promise) Error: Error!
// エラーをキャッチできていない
// catchメソッドの場合
getPromise().catch(e => console.log('エラーをキャッチ'))
// エラーをキャッチ
await
の場合
しかし、関数の前にawait
をつけると話は変わってきます。
await
をつけたプロミス内で発生したエラーは、なんと通常のtry-catch
で捕捉できます。
try {
await getPromise() // awaitをつける
} catch (e) {
console.log('エラーをキャッチ')
}
// エラーをキャッチ
catch
メソッドとawait
関数の呼び出しの後にcatch
メソッドを繋げ、それをawait
してもエラーは捕捉できます。
try {
// awaitをつける & catchメソッドでハンドリング
await getPromise()
.catch(e => console.log('エラーをキャッチ in catchメソッド'))
} catch (e) {
console.log('エラーをキャッチ')
}
// エラーをキャッチ in catchメソッド
// try-catchのcatch句内での処理は実行されない
// 普通にこれだけでもOK
await getPromise()
.catch(e => console.log('エラーをキャッチ in catchメソッド'))
// エラーをキャッチ in catchメソッド
しかし、処理の流れがどうなるのかが直感的ではないので、特別な理由がない限りはやるべきではないと思います。
何が推奨?
非同期処理内でのエラーを補足するとき、推奨度合いは大まかに以下の表のようになると思います。
呼び出し方法 | エラーの補足方法 | 推奨度合い |
---|---|---|
await での呼び出し |
try-catch |
推奨 |
通常の呼び出し |
catch メソッド |
OK(NG派もいる) |
await での呼び出し |
catch メソッド |
わかりづらいので非推奨 |
通常の呼び出し | try-catch |
絶対にNG: そもそも補足できない |
とは言ったものの、最後を除いては人によると思います。
プロミスを返す関数にasync
をつけたら?
async
をつけなくともプロミスを返す関数は定義できます。
例えばPromise
のコンストラクタからプロミスを作成した場合です。
function getPromise() {
return new Promise((resolve, reject) => {
// 非同期で実行したい処理
})
}
現在のgetPromise
関数の戻り値は、コード内のnew Promise
で作ったプロミスと同じです。
では、この関数の先頭にasync
をつけたらどうなるでしょうか。
async
をつけた関数は、コード内でnew
されたプロミスとは別の新しいプロミスを返します。
これはコード内のプロミスをラップしたものなので、実際に呼び出したときの動作は変わりません。
async function getPromiseAsync() {
// このプロミスをラップしたプロミスが戻り値になる
return new Promise((resolve, reject) => {
// 非同期で実行したい処理
resolve()
})
}
// 関数の動作は変わらない
await getPromise()
await getPromiseAsync()
詳細はMDNをご覧ください。