--- title: 非同期関数 slug: Web/JavaScript/Reference/Statements/async_function tags: - Example - Function - JavaScript - Language feature - Statement - 文 - 言語機能 - 関数 translation_of: Web/JavaScript/Reference/Statements/async_function ---
async function
宣言は、 非同期関数 — {{jsxref("Global_Objects/AsyncFunction","AsyncFunction")}} オブジェクトである関数を定義します。非同期関数はイベントループを介して他のコードとは別に実行され、結果として暗黙の {{jsxref("Promise")}} を返します。ただし、非同期関数を使用したコードの構文および構造は、通常の同期関数と似たものになります。
{{jsxref("Operators/async_function", "async function 式", "", 1)}} を使用して非同期関数を定義することもできます。
async function name([param[, param[, ...param]]]) { statements }
name
param
statements
{{jsxref("Promise")}} で、非同期関数から返される値で解決するか、または非同期関数内の捕捉されなかった例外で拒否します。
非同期関数は、 {{jsxref("Operators/await", "await")}} 式を含むことができます。これは非同期関数の実行を一時停止し、 Promise
の解決を待ちます。そして非同期関数の実行を再開し、解決された値を返します。
キーワード await
は、非同期関数の中でのみ有効です。非同期関数の外で使用した場合は {{jsxref("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 と同様
sequentialStart
では、最初の await
のために実行が 2 秒間待機し、 2 つ目の await
のためにさらに 1 秒間待機します。 2 つ目のタイマーは最初のタイマーが起動している間は作成されません。コードは 3 秒後に終了します。
concurrentStart
では、両方のタイマーが作成され、両方とも await
される、すなわち待機させられます。タイマーは同時に実行されているため、 3 秒後ではなく 2 秒後に、すなわち最も遅いタイマーにあわせて終了します。
しかし、 await
の呼び出しは依然として逐次処理であり、これは 2 つ目の await
が 1 つ目の終了まで待つことを意味します。このケースでは、最も速いタイマーが最も遅いタイマーのあとに処理されることになります。
もし複数の処理を完全に並列に実行したい場合は、上記コード中の parallel
のように await Promise.all([job1(), job2()])
を使わなければなりません。
多くの非同期関数は Promise を用いて通常の関数として書くことができます。しかし async
関数はエラー処理において少し簡単です。
concurrentStart
とconcurrentPromise
のどちらも関数としては同値です。
concurrentStart
では、 await
されたいずれかの関数呼び出しが失敗すれば、例外は自動的にキャッチされ、非同期関数の実行が中断され、暗黙的に返される Promise を経由してエラーが呼び出し元へ伝えられます。Promise
の面倒を見なければなりません。これは concurrentPromise
では Promise.all([]).then()
が返す Promise を return
することを意味します。実は、この例の前のバージョンはこれをやり忘れていました!しかしながら非同期関数も誤ってエラーを飲み込んでしまうことがあります。
上記の parallel
という非同期関数を例にしてみましょう。もしこれが Promise.all([])
呼び出しの結果を await
(もしくは return
) しなければ、任意のエラーは伝わりません。
parallelPromise
の例は簡潔に見えるものの、エラーをまったくハンドルしていません!同じことをするには、やはり return Promise.all[()]
が必要になります。
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
の返値は暗黙的に {{jsxref("Promise.resolve")}} でラップされているからです。
返値が {{jsxref("Promise.resolve")}} で暗黙にラッピングされるとはいえ、 return await promiseValue
が return 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
に解決されるのではなく、関数が返した {{jsxref("Promise")}} が拒否されてしまいます。
これは、 return foo;
と return await foo;
の微妙な違いを強調しています。 - return foo
はすぐに foo
を返し、 foo
が拒否する Promise であっても例外を発生させません。 return await foo
は、それが Promise であれば foo
が解決するか拒否するかを待ち、拒否した場合は返す前に例外を発生させます。
仕様書 |
---|
{{SpecName('ESDraft', '#sec-async-function-definitions', 'async function')}} |
{{Compat("javascript.statements.async_function")}}