JavaScript

async function(非同期関数の宣言)

 編集:2022.07.29 by T.A.

非同期関数は async キーワードで宣言され、その中で await キーワードを使うことができます。async および await キーワードを使用することで、プロミス ベースの非同期の動作を、プロミス チェーンを明示的に構成する必要なく、よりすっきりとした方法で書くことができます。→AsyncFunction コンストラクター

非同期関数はイベント ループを介して他のコードとは別に実行され、結果として暗黙の Promise を返します。ただし、非同期関数を使用したコードの構文および構造は、通常の同期関数と似たものになります。

async function 式 を使用して非同期関数を定義することもできます。

async function name( [param[, param[, ...param]]] ) {
 ステートメント
}
name関数名 関数の名前。
param 関数に渡す引数名。
ステートメント 関数の本体を構成する文。
戻り値

Promise で、非同期関数から返される値で解決するか、または非同期関数内の捕捉されなかった例外で拒否します。

解説

非同期関数は、await 式を含むことができます。これは非同期関数の実行を一時停止し、Promise の解決を待ちます。そして非同期関数の実行を再開し、解決された値を返します。

キーワード await は、非同期関数の中でのみ有効です。非同期関数の外で使用した場合は SyntaxError となります。

非同期関数が一時停止している間、呼び出し側の関数は実行が続きます(非同期関数から返される暗黙の Promise を受け取ります)。

async/await の目的は、Promise を同期的に使用する動作を簡素化し、Promise のグループに対して何らかの動作を実行することです。Promise が構造化コールバックに似ているのと同様に、async/await はジェネレーターと Promise を組み合わせたものに似ています。

シンプルな例
//コンソールにて
function resolveAfter2Seconds() {
  console.log("starting slow promise")
  return new Promise(resolve => {
    setTimeout(function() {
      resolve("slow")
      console.log("slow promise is done")
    }, 2000)
  })
}

function resolveAfter1Second() {
  console.log("starting fast promise")
  return new Promise(resolve => {
    setTimeout(function() {
      resolve("fast")
      console.log("fast promise is done")
    }, 1000)
  })
}

async function sequentialStart() {
  console.log('==SEQUENTIAL START==')

  // 1. ここは即時実行される
  const slow = await resolveAfter2Seconds()
  console.log(slow) // 2. ここは 1. の2秒後に実行される

  const fast = await resolveAfter1Second()
  console.log(fast) // 3. ここは 1. の3秒後に実行される
}

async function concurrentStart() {
  console.log('==CONCURRENT START with await==');
  const slow = resolveAfter2Seconds() // 即時実行
  const fast = resolveAfter1Second() // 即時実行

  // 1. ここは即時実行される
  console.log(await slow) // 2. ここは 1. の2秒後に実行される
  console.log(await fast) // 3. ここは 1. の2秒後(2.の直後)に実行される
}

function concurrentPromise() {
  console.log('==CONCURRENT START with Promise.all==')
  return Promise.all([resolveAfter2Seconds(), resolveAfter1Second()]).then((messages) => {
    console.log(messages[0]) // slow
    console.log(messages[1]) // fast
  })
}

async function parallel() {
  console.log('==PARALLEL with await Promise.all==')

  // 2つの jobs を並列に実行し両方が完了するのを待つ
  await Promise.all([
      (async()=>console.log(await resolveAfter2Seconds()))(),
      (async()=>console.log(await resolveAfter1Second()))()
  ])
}

// この関数はエラーハンドリングをしていません。後述の注意書きを参照してください。
function parallelPromise() {
  console.log('==PARALLEL with Promise.then==')
  resolveAfter2Seconds().then((message)=>console.log(message))
  resolveAfter1Second().then((message)=>console.log(message))
}

sequentialStart() // 2秒後に "slow" をログ出力し、その1秒後に "fast" をログ出力する

// 見やすくするため setTimeout で直前の処理が終わるのを待つ
setTimeout(concurrentStart, 4000) // 2秒後に "slow" と "fast" をログ出力する

// 直前の処理を待つ
setTimeout(concurrentPromise, 7000) // concurrentStart と同様

// 直前の処理を待つ
setTimeout(parallel, 10000) // 本当に並列処理となるため1秒後に "fast" とログ出力し、その1秒後に "slow" とログ出力する

// 直前の処理を待つ
setTimeout(parallelPromise, 13000) // parallel と同様
await と並列性

sequentialStart では、最初の await のために実行が 2 秒間待機し、2 つ目の await のためにさらに 1 秒間待機します。2 つ目のタイマーは最初のタイマーが起動している間は作成されません。コードは 3 秒後に終了します。

concurrentStart では、両方のタイマーが作成され、両方とも await される、すなわち待機させられます。タイマーは同時に実行されているため、3 秒後ではなく 2 秒後に、すなわち最も遅いタイマーにあわせて終了します。 しかし、await の呼び出しは依然として逐次処理であり、これは 2 つ目の await が 1 つ目の終了まで待つことを意味します。このケースでは、最も速いタイマーが最も遅いタイマーのあとに処理されることになります。

もし複数の処理を完全に並列に実行したい場合は、上記コード中の parallel のように await Promise.all([job1(), job2()]) を使わなければなりません。

¶以下省く


テスト