Skip to content

Commit

Permalink
2024/07/26 時点の英語版に基づき更新
Browse files Browse the repository at this point in the history
  • Loading branch information
mfuji09 committed Oct 17, 2024
1 parent 81edb83 commit 1aee678
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 86 deletions.
159 changes: 87 additions & 72 deletions files/ja/web/javascript/reference/statements/async_function/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
title: async function
slug: Web/JavaScript/Reference/Statements/async_function
l10n:
sourceCommit: d85a7ba8cca98c2f6cf67a0c44f0ffd467532f20
sourceCommit: 3f91fdcc678991410f4f5adcbff44d1b3b1ede88
---

{{jsSidebar("Statements")}}

**`async function`** 宣言は非同期関数を宣言し、その中で `await` キーワードを使うことができます`async` および `await` キーワードを使用することで、プロミスベースの非同期の動作を、プロミスチェーンを明示的に構成する必要なく、よりすっきりとした方法で書くことができます。
**`async function`** 宣言は、与えられた名前で新しい非同期関数の{{Glossary("binding","バインド")}}を作成します。その関数の本体の中では `await` キーワードを使うことができ、ます`async` および `await` キーワードを使用することで、プロミスベースの非同期の動作を、プロミスチェーンを明示的に構成する必要なく、よりすっきりとした方法で書くことができます。

非同期関数は{{jsxref("Operators/async_function", "式として", "", 1)}}も定義することができます
非同期関数は [`async function`](/ja/docs/Web/JavaScript/Reference/Operators/async_function)を使用して定義することもできます

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

Expand All @@ -22,26 +22,27 @@ async function name(param0) {
async function name(param0, param1) {
statements
}
async function name(param0, param1, /* … ,*/ paramN) {
async function name(param0, param1, /* …, */ paramN) {
statements
}
```

> [!NOTE]
> 改行を `async``function` の間に入れてはいけません。そうしないとセミコロンが[自動的に挿入され](/ja/docs/Web/JavaScript/Reference/Lexical_grammar#自動セミコロン挿入)`async` が識別子となり、残りが `function` 宣言となります。
### 引数

- `name`
- : 関数の名前です。
- `param` {{optional_inline}}
- : 関数に渡す引数の名前です
- : 関数の正式な引数の名前です。引数の構文については、[関数リファレンス](/ja/docs/Web/JavaScript/Guide/Functions#関数の引数)を参照してください
- `statements` {{optional_inline}}
- : 関数の本体を構成する文です。`await` の仕組みを使用することができます。

### 返値

{{jsxref("Promise")}} で、非同期関数から返される値で解決するか、または非同期関数内で捕捉されなかった例外で拒否されます。

## 解説

`async function` 宣言は、{{jsxref("AsyncFunction")}} オブジェクトを作成します。非同期関数が呼び出されるたびに、新しいプロミス ({{jsxref("Promise")}}) が返され、非同期関数によって返された値で解決されます。または、非同期関数内で捕捉されなかった例外で拒否されます。

非同期関数には、 {{jsxref("Operators/await", "await")}} 式を置くことができます。 await 式は返されたプロミスが履行されるか拒否されるまで実行を中断することで、プロミスを返す関数をあたかも同期しているかのように動作させます。プロミスの解決済みの値は、await 式の返値として扱われます。`async``await` を使用すると、非同期コードに通常の `try` / `catch` ブロックを使用することができます。

> [!NOTE]
Expand Down Expand Up @@ -69,32 +70,26 @@ function foo() {
}
```

> [!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
> ```
たとえ非同期関数の返値が `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 式がある場合、非同期関数は常に非同期に完了します
非同期関数の本体は、0 個以上の await 式で分割されていると考えることができます。最上位のコードは、最初の await 式(ある場合)まで含めて同期的に実行されます。この方法では、await 式のない非同期関数は同期的に実行されます。しかし、関数本体内に await 式がある場合、非同期関数は常に非同期的に完了します

例:

Expand Down Expand Up @@ -145,7 +140,7 @@ async function foo() {
foo().catch(() => {}); // すべてのエラーを浅くしようとする...
```

`async function` 宣言はスコープの上端まで[巻き上げられ](/ja/docs/Glossary/Hoisting)、スコープのどこからでも呼び出すことができます
`async function` 宣言は、{{jsxref("Statements/function", "function")}} 宣言と似た挙動をします。つまり、[巻き上げ](/ja/docs/Glossary/Hoisting)によりスコープの先頭に移動し、スコープ内のどこからでも呼び出すことができます。また、特定のコンテキストでのみ再宣言することができます

##

Expand Down Expand Up @@ -173,74 +168,87 @@ function resolveAfter1Second() {
}

async function sequentialStart() {
console.log("==SEQUENTIAL START==");
console.log("== sequentialStart 開始 ==");

// 1. これは即時実行される
const slow = await resolveAfter2Seconds();
console.log(slow); // 2. これは 1. の 2 秒後に実行される
// 1. タイマーの起動、完了後にログ出力
const slow = resolveAfter2Seconds();
console.log(await slow);

const fast = await resolveAfter1Second();
console.log(fast); // 3. これは 1. の 3 秒後に実行される
// 2. 前回のタイマーを待った後で次のタイマーを開始
const fast = resolveAfter1Second();
console.log(await fast);

console.log("== sequentialStart 終了 ==");
}

async function concurrentStart() {
console.log("==CONCURRENT START with await==");
const slow = resolveAfter2Seconds(); // ただちにタイマーを起動
const fast = resolveAfter1Second(); // ただちにタイマーを起動
async function sequentialWait() {
console.log("== sequentialWait 開始 ==");

// 1. 2 つのタイマーを同時に開始するには、お互いを待つ必要はない
const slow = resolveAfter2Seconds();
const fast = resolveAfter1Second();

// 1. これは即時実行される
console.log(await slow); // 2. これは 1. の 2 秒後に実行される
console.log(await fast); // 3. fast はすでに解決しているので、これは 1. の 2 秒後 (2.の直後) に実行される
// 2. slow タイマーが完全に終了するまで待ち、その後、結果をログ出力する
console.log(await slow);
// 3. fast タイマーが完全に終了するまで待ち、その後、結果をログ出力する
console.log(await fast);

console.log("== sequentialWait 終了 ==");
}

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 concurrent1() {
console.log("== concurrent1 開始 ==");

// 1. 2 つのタイマーを同時に開始し、どちらも完了するのを待つ
const results = await Promise.all([
resolveAfter2Seconds(),
resolveAfter1Second(),
]);
// 2. それぞれログ出力する
console.log(results[0]);
console.log(results[1]);

console.log("== concurrent1 終了 ==");
}

async function parallel() {
console.log("==PARALLEL with await Promise.all==");
async function concurrent2() {
console.log("== concurrent2 開始 ==");

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

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

// 直前の処理を待つ
setTimeout(concurrentStart, 4000); // 2 秒後に "slow" と "fast" をログ出力する
setTimeout(sequentialWait, 4000); // 2 秒後に "slow" と "fast" をログ出力する

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

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

#### await と並列性
#### await と並行性

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

`concurrentStart` では、両方のタイマーが作成され、両方とも `await` される、すなわち待機させられます。タイマーは同時に実行されているため、 3 秒後ではなく 2 秒後に、すなわち最も遅いタイマーにあわせて終了します。
`sequentialWait` では、両方のタイマーが作成され、両方とも `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) の呼び出しで待つ必要があります。
複数の処理を安全に並行に実行したい場合は、{{jsxref("Promise.all()")}} または {{jsxref("Promise.allSettled()")}} の呼び出しで待つ必要があります。

> [!WARNING]
> 関数 `concurrentStart``concurrentPromise` は機能的に同等ではありません。
> 関数 `sequentialWait``concurrent1` は機能的に同等ではありません。
>
> `concurrentStart` では、プロミス `fast` がプロミス `slow` の履行よりも前に拒否された場合、呼び出し元が catch 節を構成しているかどうかにかかわらず、プロミスの拒否が処理されないというエラーが発生します。
> `sequentialWait` では、プロミス `fast` がプロミス `slow` の履行よりも前に拒否された場合、呼び出し元が catch 節を構成しているかどうかにかかわらず、プロミスの拒否が処理されないというエラーが発生します。
>
> `concurrentPromise` では、`Promise.all` がプロミスチェーンを一括して配線します。つまり、操作はプロミスの拒否の順番に関係なくすばやく失敗し、エラーは構成されたプロミスチェーン内で常に発生するため、通常の方法で捕捉することができます。
> `concurrent1` では、`Promise.all` がプロミスチェーンを一括して配線します。つまり、操作はプロミスの拒否の順番に関係なくすばやく失敗し、エラーは構成されたプロミスチェーン内で常に発生するため、通常の方法で捕捉することができます。
### プロミスチェーンを非同期関数で書き換える

Expand Down Expand Up @@ -277,7 +285,7 @@ async function getProcessedData(url) {
}
```

二番目の例では、有効であるにもかかわらず、 `await` 文が `return` キーワードの後にないことに注意してください。非同期関数の返値は、(この例のように)既にプロミスでない限り、暗黙的に {{jsxref("Promise.resolve")}} でラップされるからです。
2 番目の例では、有効であるにもかかわらず、`await` 文が `return` キーワードの後にないことに注意してください。非同期関数の返値は、(この例のように)既にプロミスになっていない場合、暗黙的に {{jsxref("Promise.resolve")}} でラップされるからです。

## 仕様書

Expand All @@ -289,7 +297,14 @@ async function getProcessedData(url) {

## 関連情報

- {{jsxref("Operators/async_function", "非同期関数式", "", 1)}}
- {{jsxref("AsyncFunction")}} オブジェクト
- [関数](/ja/docs/Web/JavaScript/Guide/Functions)ガイド
- [プロミスの使用](/ja/docs/Web/JavaScript/Guide/Using_promises)ガイド
- [関数](/ja/docs/Web/JavaScript/Reference/Functions)
- {{jsxref("AsyncFunction")}}
- [`async function`](/ja/docs/Web/JavaScript/Reference/Operators/async_function)
- {{jsxref("Statements/function", "function")}}
- {{jsxref("Statements/function*", "function*")}}
- {{jsxref("Statements/async_function*", "async function*")}}
- {{jsxref("Operators/await", "await")}}
- [Decorating Async JavaScript Functions](https://innolitics.com/10x/javascript-decorators-for-promise-returning-functions/) (_innolitics.com_)
- {{jsxref("Promise")}}
- [Decorating async JavaScript functions](https://innolitics.com/10x/javascript-decorators-for-promise-returning-functions/) (innolitics.com, 2016)
Loading

0 comments on commit 1aee678

Please sign in to comment.