async function

async function 宣言は、 非同期関数AsyncFunction オブジェクトである関数を定義します。非同期関数はイベントループを介して他のコードとは別に実行され、結果として暗黙の Promise を返します。ただし、非同期関数を使用したコードの構文および構造は、通常の同期関数と似たものになります。

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

構文

async function name([param[, param[, ...param]]]) {
   statements
}

引数

name
関数名。
param
関数に渡す引数名。
statements
関数の本体を構成する文。

返値

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()]) を使わなければなりません。

async/await と Promise.then およびエラー処理

多くの非同期関数は Promise を用いて通常の関数として書くことができます。しかし async 関数はエラー処理において少し簡単です。

concurrentStartconcurrentPromiseのどちらも関数としては同値です。

  • concurrentStart では、 await されたいずれかの関数呼び出しが失敗すれば、例外は自動的にキャッチされ、非同期関数の実行が中断され、暗黙的に返される Promise を経由してエラーが呼び出し元へ伝えられます。
  • 同じことが Promise の場合にも起こり、関数は、関数の完了をとらえて戻ってくる Promise の面倒を見なければなりません。これは concurrentPromise では Promise.all([]).then() が返す Promise を return することを意味します。実は、この例の前のバージョンはこれをやり忘れていました!

しかしながら非同期関数も誤ってエラーを飲み込んでしまうことがあります。

上記の parallel という非同期関数を例にしてみましょう。もしこれが Promise.all([]) 呼び出しの結果を await (もしくは return) しなければ、任意のエラーは伝わりません。

parallelPromise の例は簡潔に見えるものの、エラーをまったくハンドルしていません!同じことをするには、やはり return Promise.all[()] が必要になります。

promise チェーンをasync function で 書き換える

Promise を返す API は Promise チェーンで解決され、関数を複数の部品に分割できます。次のコードを想定してください。

function getProcessedData(url) {
  return downloadData(url) // returns a promise
    .catch(e => {
      return downloadFallbackData(url)  // returns a promise
    })
    .then(v => {
      return processDataInWorker(v)  // returns a promise
    }) 
}

次のように 1 つの async 関数に書き直すことができます。

async function getProcessedData(url) {
  let v
  try {
    v = await downloadData(url)
  } catch(e) {
    v = await downloadFallbackData(url)
  }
  return processDataInWorker(v)
}

上記の例では、 return ステートメント上に await ステートメントがないことに注目してください。なぜなら、async function の返値は暗黙的に Promise.resolve でラップされているからです。

return await promiseValue と return promiseValue

返値が Promise.resolve で暗黙にラッピングされるとはいえ、 return await promiseValuereturn promiseValue と機能的に等価である訳ではありません。

上記のコードを以下のように書き直したと想像してください。これは processDataInWorker がエラーで拒否した場合に null を返します。

async function getProcessedData(url) {
  let v
  try {
    v = await downloadData(url)
  } catch(e) {
    v = await downloadFallbackData(url)
  }
  try {
    return await processDataInWorker(v)  // Note the `return await` vs. just `return`
  } catch (e) {
    return null
  }
}

return processDataInWorker(v) と記述すると、 processDataInWorker(v) が拒否した場合に null に解決されるのではなく、関数が返した Promise が拒否されてしまいます。

これは、 return foo;return await foo; の微妙な違いを強調しています。 - return foo はすぐに foo を返し、 foo が拒否する Promise であっても例外を発生させません。 return await foo は、それが Promise であれば foo が解決するか拒否するかを待ち、拒否した場合は返す前に例外を発生させます。

仕様書

仕様書
ECMAScript (ECMA-262)
async function の定義

ブラウザーの互換性

Update compatibility data on GitHub
デスクトップモバイルサーバー
ChromeEdgeFirefoxInternet ExplorerOperaSafariAndroid webviewAndroid 版 ChromeAndroid 版 FirefoxAndroid 版 OperaiOSのSafariSamsung InternetNode.js
async functionChrome 完全対応 55Edge 完全対応 15Firefox 完全対応 52IE 未対応 なしOpera 完全対応 42Safari 完全対応 10.1WebView Android 完全対応 55Chrome Android 完全対応 55Firefox Android 完全対応 52Opera Android 完全対応 42Safari iOS 完全対応 10.3Samsung Internet Android 完全対応 6.0nodejs 完全対応 7.6.0
完全対応 7.6.0
完全対応 7.0.0
無効
無効 From version 7.0.0: this feature is behind the --harmony runtime flag.

凡例

完全対応  
完全対応
未対応  
未対応
ユーザーが明示的にこの機能を有効にしなければなりません。
ユーザーが明示的にこの機能を有効にしなければなりません。

関連情報