0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ひとりJavaScriptAdvent Calendar 2024

Day 16

【JavaScript】async / awaitって何?

Last updated at Posted at 2024-12-15

awaitって何?

awaitプロミスの履行を待つ構文です。
これを使うと、Promisethenをよりすっきりとしたコードに置き換えることができます。

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() {
  // 処理
}

そして、awaitasyncがついた関数の中でしか使えません。
例えば、次のコードは構文エラーとなります。

// 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をご覧ください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?