JavaScriptの非同期処理の定番!async/await概要・挙動まとめ

JavaScript

async/awaitを利用することでPromiseの操作を簡潔に記述できます。JavaScriptで非同期処理を実装する際はasync/awaitを利用するケースがほとんどです。

今回はasync/awaitの概要についてまとめたので紹介をします。

なお、async/awaitを理解するにはPromiseの知識が必須です。
Promiseの概要についてはJavaScriptの非同期処理で理解必須!Promiseの概要・挙動まとめで紹介をしています。

JavaScriptの非同期処理で理解必須!Promiseの概要・挙動まとめ

2021年2月28日

asyncについて

関数にasyncをつけることで非同期関数となります。
ここではasyncを利用した関数(async関数)の概要について紹介します。

async関数はPromiseオブジェクトを返す

async関数でreturnをした場合fulfilledなPromiseオブジェクトが返されます。
一方、async関数で例外がthrowされた場合はrejectedなPromiseオブジェクトが返されます。

const asyncFulfilled = async() => {
  return 'fulfilled'
}

asyncFulfilled()
  .then(result => console.log(`Resut: ${result}`))
  .catch(message => console.log(`NG! ${message}`))
// Resut: fulfilled
const asyncRejected = async() => {
  throw new Error('rejected')
}

asyncRejected()
  .then(result => console.log(`Resut: ${result}`))
  .catch(message => console.log(`NG! ${message}`))
// NG! Error: rejected

awaitについて

async/awaitのawaitの概要について紹介します。

awaitはasync関数の中でのみ利用可能

awaitはasync関数内に定義されているPromiseの結果(成功 or 失敗)を待つ演算子です。
つまり、awaitはasync関数の中でのみ利用が可能です。

async関数以外でawaitを利用すると以下のようにエラーが発生します。

const asyncExample = (number) => {
  console.log(await Promise.resolve(number))
}
asyncExample(2)
// console.log(await Promise.resolve(number))
// ^^^^^
//
// SyntaxError: missing ) after argument list

async関数の中であればawaitが使えます。

const asyncExample = async(number) => {
  console.log(await Promise.resolve(number))
}

asyncExample(2)
// 2

awaitはfulfilledなPromiseオブジェクトのデータを取得する

たとえば『入力値を2倍にする(ただし、入力値が10以上だったら例外を発生させる)』というメソッドがPromiseを利用して実装されていたとします。
当該メソッドの実行結果をasync/awaitを利用して取得するには以下のようになります。

const double = (number) => {
  return new Promise((resolve, reject) => {
    (number < 10) ? resolve(number * 2) : reject(new Error(`${number}は10以上の数です`));
  })
}

const asyncDouble = async(number) => console.log(await double(number))

asyncDouble(2)
// 4

async関数でPromiseオブジェクトを返す場合は以下のようになります。
fulfilledなPromiseオブジェクトがasync関数から返されるので、then()を利用してデータを取得します。

const double = (number) => {
  return new Promise((resolve, reject) => {
    (number < 10) ? resolve(number * 2) : reject(new Error(`${number}は10以上の数です`));
  })
}

const asyncDouble = async(number) => await double(number)

asyncDouble(2).then(result => console.log(`Resut: ${result}`))
// Resut: 4

async/awaitの部分(asyncDoubleメソッド)をthen()で書き直すと以下のようになります。

const double = (number) => {
  return new Promise((resolve, reject) => {
    (number < 10) ? resolve(number * 2) : reject(new Error(`${number}は10以上の数です`));
  })
}

const doubleFunction = (number) => double(number).then((result) => result)

doubleFunction(2).then(result => console.log(`Resut: ${result}`))
// Resut: 4

awaitで取得したPromiseオブジェクトがrejectedの場合、例外がthrowされる

以下の2つのコードは等価になります。

// awaitでrejectedなPromiseオブジェクトを受け取った場合
const asyncAwaitRejected = async() => {
  await Promise.reject(new Error('rejected'))
}

// 上記のコードは以下のように解釈される
const asyncAwaitRejected = async() => {
  throw new Error('rejected')
}

awaitで取得したPromiseオブジェクトがrejectedの場合、async関数内で例外がthrowされることになります。
つまり、async関数はrejectedなPromiseオブジェクトを返すことになります。

rejectedなPromiseオブジェクトがasync関数から返されるので、catch()を利用してデータを取得します。

const asyncAwaitRejected = async() => {
  await Promise.reject(new Error('rejected'))
}

asyncAwaitRejected().catch(error => console.log(error.message))
// rejected

たとえば、先ほど紹介した『入力値を2倍にする(ただし、入力値が10以上だったら例外を発生させる)』というメソッドの利用時に例外が発生した場合、async関数の結果は以下のようになります。

const double = (number) => {
  return new Promise((resolve, reject) => {
    (number < 10) ? resolve(number * 2) : reject(new Error(`${number}は10以上の数です`));
  })
}

const asyncDouble = async(number) => await double(number)

asyncDouble(10)
  .then(result => console.log(`Resut: ${result}`))
  .catch(message => console.log(`NG! ${message}`))
// NG! Error: 10は10以上の数です

async/awaitのエラーハンドリング方法

async/awaitで利用されるエラーハンドリングの方法を2つ紹介します。

try-catchパターン

awaitが実行される箇所をtry-catchで囲む方法です。
一般的なエラーハンドリング方法ですが、『tryのスコープ外で戻り値を利用する場合、letの宣言が必要』『tryのスコープが広くなりすぎると例外発生箇所の特定が難しくなる』などのデメリットがあります。

const double = (number) => {
  return new Promise((resolve, reject) => {
    (number < 10) ? resolve(number * 2) : reject(new Error(`${number}は10以上の数です`));
  })
}

const asyncDouble = async(number) => {
  let result
  try {
    result = await double(number)
  } catch(error) {
    throw new Error(error.message)
  }
  return result
}

asyncDouble(2)
  .then(result => console.log(`Resut: ${result}`))
  .catch(message => console.log(`NG! ${message}`))
// Resut: 4

asyncDouble(10)
  .then(result => console.log(`Resut: ${result}`))
  .catch(message => console.log(`NG! ${message}`))
// NG! Error: 10は10以上の数です

await-catchパターン

awaitを利用している箇所にcatch()を追加する方法です。
await-catchパターンではPromiseがrejectedになった場合のみ例外が発生するため、例外箇所を特定しやすいというメリットがあります。

const double = (number) => {
  return new Promise((resolve, reject) => {
    (number < 10) ? resolve(number * 2) : reject(new Error(`${number}は10以上の数です`));
  })
}

const asyncDouble = async(number) => {
  const result = await double(number).catch(error => {
    throw new Error(error.message)
  })
  return result
}

asyncDouble(2)
  .then(result => console.log(`Resut: ${result}`))
  .catch(message => console.log(`NG! ${message}`))
// Resut: 4

asyncDouble(10)
  .then(result => console.log(`Resut: ${result}`))
  .catch(message => console.log(`NG! ${message}`))
// NG! Error: 10は10以上の数です

まとめ

async/awaitまとめ
  • returnをしたasync関数はfulfilledなPromiseオブジェクトを返す
  • 例外が発生したasync関数はrejectedなPromiseオブジェクトを返す
  • awaitはasync関数の中でのみ利用可能
  • awaitはfulfilledなPromiseオブジェクトの結果を取得する
  • async/awaitのエラーハンドリングはtry-catchもしくはawait-catchパターンを利用する

Twitter(@nishina555)やってます。フォローしてもらえるとうれしいです!