From f72358bab2839cbc638a1adbe331ff37cafd4cd5 Mon Sep 17 00:00:00 2001 From: Masahiro FUJIMOTO Date: Sun, 12 Sep 2021 01:52:23 +0900 Subject: Statements/async_function を同期 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 2021/09/07 時点の英語版に同期 --- .../reference/statements/async_function/index.html | 259 ------------------- .../reference/statements/async_function/index.md | 280 +++++++++++++++++++++ 2 files changed, 280 insertions(+), 259 deletions(-) delete mode 100644 files/ja/web/javascript/reference/statements/async_function/index.html create mode 100644 files/ja/web/javascript/reference/statements/async_function/index.md (limited to 'files/ja/web') diff --git a/files/ja/web/javascript/reference/statements/async_function/index.html b/files/ja/web/javascript/reference/statements/async_function/index.html deleted file mode 100644 index 2c708ddbc7..0000000000 --- a/files/ja/web/javascript/reference/statements/async_function/index.html +++ /dev/null @@ -1,259 +0,0 @@ ---- -title: 非同期関数 -slug: Web/JavaScript/Reference/Statements/async_function -tags: - - Example - - Function - - JavaScript - - Language feature - - Statement - - 文 - - 言語機能 - - 関数 -translation_of: Web/JavaScript/Reference/Statements/async_function ---- -
{{jsSidebar("Statements")}}
- -

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

- -
-

{{jsxref("Operators/async_function", "async function 式", "", 1)}} を使用して非同期関数を定義することもできます。

-
- -
{{EmbedInteractiveExample("pages/js/statement-async.html", "taller")}}
- - - -

構文

- -
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 と同様
-
- -

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のどちらも関数としては同値です。

- - - -

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

- -

上記の 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 の返値は暗黙的に {{jsxref("Promise.resolve")}} でラップされているからです。

- -
-

return await promiseValue と return promiseValue

- -

返値が {{jsxref("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 に解決されるのではなく、関数が返した {{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")}}

-
- -

関連情報

- - diff --git a/files/ja/web/javascript/reference/statements/async_function/index.md b/files/ja/web/javascript/reference/statements/async_function/index.md new file mode 100644 index 0000000000..a501540198 --- /dev/null +++ b/files/ja/web/javascript/reference/statements/async_function/index.md @@ -0,0 +1,280 @@ +--- +title: 非同期関数 +slug: Web/JavaScript/Reference/Statements/async_function +tags: + - Example + - 関数 + - JavaScript + - 言語機能 + - 文 +browser-compat: javascript.statements.async_function +translation_of: Web/JavaScript/Reference/Statements/async_function +--- +{{jsSidebar("Statements")}} + +非同期関数は `async` キーワードで宣言され、その中で `await` キーワードを使うことができます。 `async` および `await` キーワードを使用することで、プロミスベースの非同期の動作を、プロミスチェーンを明示的に構成する必要なく、よりすっきりとした方法で書くことができます。 + +非同期関数は{{jsxref("Operators/async_function", "式として", "", 1)}}も定義することができます。 + +{{EmbedInteractiveExample("pages/js/statement-async.html", "taller")}} + +## 構文 + +```js +async function name([param[, param[, ...param]]]) { + statements +} +``` + +### 引数 + +- `name` + - : 関数の名前。 +- `param` + - : 関数に渡す引数名。 +- `statements` + - : 関数の本体を構成する文。 + +### 返値 + +{{jsxref("Promise")}} で、非同期関数から返される値で解決するか、または非同期関数内で捕捉されなかった例外で拒否されます。 + +## 解説 + +非同期関数には、 {{jsxref("Operators/await", "await")}} 式を置くことができます。 await 式は返されたプロミスが履行されるか拒否されるまで実行を中断することで、プロミスを返す関数をあたかも同期しているかのように動作させます。プロミスの解決済みの値は、await 式の返値として扱われます。`async` と `await` を使用すると、非同期コードに通常の `try` / `catch` ブロックを使用することができます。 + +> **Note:** キーワード `await` は、通常の JavaScript コード内の非同期関数内でのみ有効です。非同期関数の外で使用した場合は {{jsxref("SyntaxError")}} が発生します。 +> +> `await` は [JavaScript モジュール](/ja/docs/Web/JavaScript/Guide/Modules)では単独で使用することができます。 + +> **Note:** `async`/`await` の目的は、プロミスベースの API を利用するのに必要な構文を簡素化することです。 `async`/`await` の動作は、[ジェネレーター](/ja/docs/Web/JavaScript/Guide/Iterators_and_Generators)とプロミスの組み合わせに似ています。 + +非同期関数は常にプロミスを返します。非同期関数の返値が明示的にプロミスでない場合は、暗黙的にプロミスでラップされます。 + +例: + +```js +async function foo() { + return 1 +} +``` + +これは、次のコードに似ています。 + +```js +function foo() { + return Promise.resolve(1) +} +``` + +> **Note:** +> +> 非同期関数の返値が `Promise.resolve` にラップされているかのように動作するとしても、両者は同等ではありません。 +> +> 与えられた値がプロミスであった場合、 `Promise.resolve` は同じ参照を返すのに対し、非同期関数は異なる*参照*を返します。 +> +> これは、あるプロミスと非同期関数の返値が等しいかどうかをチェックする場合に問題になることがあります。 +> +> ```js +> const p = new Promise((res, rej) => { +> res(1); +> }) +> +> async function asyncReturn() { +> return p; +> } +> +> function basicReturn() { +> return Promise.resolve(p); +> } +> +> console.log(p === basicReturn()); // true +> console.log(p === asyncReturn()); // false +> ``` + +非同期関数の本体は、 await 式で分割されていると考えることができます。最上位のコードは、 (ある場合) 最初の await 式まで、それを含めて同期的に実行されます。したがって、await 式のない非同期関数は同期的に実行されます。しかし、関数本体の中に await 式がある場合、非同期関数は常に非同期に完了します。 + +例: + +```js +async function foo() { + await 1 +} +``` + +これは次のものと等価です。 + +```js +function foo() { + return Promise.resolve(1).then(() => undefined) +} +``` + +それぞれの await 式の後のコードは、`.then` コールバックの中に存在すると考えることができます。このようにして、関数を再帰的に実行するたびに、プロミスチェーンが徐々に構築されていきます。返値はチェーンの最後のリンクになります。 + +次の例では、 2 つのプロミスを連続して待ち受けます。関数 `foo` の処理は 3 段階に分かれています。 + +1. 関数 foo の本体の最初の行は、保留中のプロミスで await 式が構成された状態で、同期的に実行されます。その後、`foo` の処理は中断され、`foo` を呼び出した関数に制御が返されます。 +2. しばらくして、最初のプロミスが履行されるか拒否されると、制御は `foo` に戻ります。 (拒否されなかった場合は) 最初のプロミスが履行された結果が await 式から返されます。ここでは `1` が `result1` に代入されます。処理は続き、2 つ目の await 式が評価されます。このときも `foo` の処理が中断され、制御が移譲されます。 +3. しばらくして、2 つ目のプロミスが履行されたか拒否されたとき、制御は `foo` に再び入ります。2 つ目のプロミスが解決した結果が 2 番目の await 式から返されます。ここでは `2` が `result2` に代入されます。制御は (もしあれば) return 式に移ります。既定の返値である `undefined` が、現在のプロミスの解決値として返されます。 + +```js +async function foo() { + const result1 = await new Promise((resolve) => setTimeout(() => resolve('1'))) + const result2 = await new Promise((resolve) => setTimeout(() => resolve('2'))) +} +foo() +``` + + プロミスチェーンが一度に構築されないことに注意してください。プロミスチェーンは、非同期関数に制御を渡したり戻したりしながら、段階的に構築されていきます。そのため、同時並行の非同期処理を行う際には、エラー処理の動作に注意しなければなりません。 + +例えば、以下のコードでは、プロミスチェーンの先に `.catch` ハンドラーが設定されていたとしても、未処理のプロミス拒否エラーが発生します。これは、`p1` から制御が戻るまで、`p2` がプロミスチェーンに「配線」されないためです。 + +```js +async function foo() { + const p1 = new Promise((resolve) => setTimeout(() => resolve('1'), 1000)) + const p2 = new Promise((_,reject) => setTimeout(() => reject('2'), 500)) +  const results = [await p1, await p2] // こうしないでください。 Promise.all または Promise.allSettled を使用してください。 +} +foo().catch(() => {}) // すべてのエラーを浅くしようとする... +``` + +## 例 + +### 非同期関数と実行順 + +```js +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. fast はすでに解決しているので、これは 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()))() + ]) +} + +sequentialStart() // 2 秒後に "slow" をログ出力し、その 1 秒後に "fast" をログ出力する + +// 直前の処理を待つ +setTimeout(concurrentStart, 4000) // 2 秒後に "slow" と "fast" をログ出力する + +// 直前の処理を待つ +setTimeout(concurrentPromise, 7000) // concurrentStart と同様 + +// 直前の処理を待つ +setTimeout(parallel, 10000) // 本当に並列処理となるため 1 秒後に "fast" とログ出力し、その 1 秒後に "slow" とログ出力する +``` + +### await と並列性 + +`sequentialStart` では、最初の `await` のために実行が 2 秒間待機し、 2 つ目の `await` のためにさらに 1 秒間待機します。 2 つ目のタイマーは最初のタイマーが起動している間は作成されません。コードは 3 秒後に終了します。 + +`concurrentStart` では、両方のタイマーが作成され、両方とも `await` される、すなわち待機させられます。タイマーは同時に実行されているため、 3 秒後ではなく 2 秒後に、すなわち最も遅いタイマーにあわせて終了します。 + しかし、 `await` の呼び出しは依然として逐次処理であり、これは 2 つ目の `await` が 1 つ目の終了まで待つことを意味します。このケースでは、最も速いタイマーが最も遅いタイマーのあとに処理されることになります。 + +複数の処理を安全に並列に実行したい場合は、 [`Promise.all`](/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/all) または +[`Promise.allSettled`](/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled) の呼び出しで待つ必要があります。 + +> **Warning:** 関数 `concurrentStart` と `concurrentPromise` は機能的に同等ではありません。 +> +> `concurrentStart` では、プロミス `fast` がプロミス `slow` の履行よりも前に拒否された場合、呼び出し元が catch 節を構成しているかどうかにかかわらず、プロミスの拒否が処理されないというエラーが発生します。 +> +> `concurrentPromise` では、`Promise.all` がプロミスチェーンを一括して配線します。つまり、操作はプロミスの拒否の順番に関係なくすばやく失敗し、エラーは構成されたプロミスチェーン内で常に発生するため、通常の方法で捕捉することができます。 + +### promise チェーンを非同期関数で書き換える + +{{jsxref("Promise")}} を返す API はプロミスチェーンを返し、関数を複数の部品に分割できます。次のコードを想定してください。 + +```js +function getProcessedData(url) { + return downloadData(url) // プロミスを返す + .catch(e => { + return downloadFallbackData(url) // プロミスを返す + }) + .then(v => { + return processDataInWorker(v) // プロミスを返す + }) +} +``` + +次のように 1 つの非同期関数に書き換えることができます。 + +```js +async function getProcessedData(url) { + let v + try { + v = await downloadData(url) + } catch(e) { + v = await downloadFallbackData(url) + } + return processDataInWorker(v) +} +``` + +二番目の例では、有効であるにもかかわらず、 `await` 文が `return` キーワードの後にないことに注意してください。非同期関数の返値は、 (この例のように) 既にプロミスでない限り、暗黙的に {{jsxref("Promise.resolve")}} でラップされるからです。 + +## 仕様書 + +{{Specifications}} + +## ブラウザーの互換性 + +{{Compat}} + +## 関連情報 + +- {{jsxref("Operators/async_function", "非同期関数式", "", 1)}} +- {{jsxref("AsyncFunction")}} オブジェクト +- {{jsxref("Operators/await", "await")}} +- ["Decorating + Async Javascript Functions" on "innolitics.com"](https://innolitics.com/10x/javascript-decorators-for-promise-returning-functions/) -- cgit v1.2.3-54-g00ecf