Skip to content

Commit 5cdd0dd

Browse files
committed
10章までdone、await対応
1 parent 64b8614 commit 5cdd0dd

File tree

4 files changed

+478
-10
lines changed

4 files changed

+478
-10
lines changed

app/pagesList.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,17 @@ export const pagesList = [
4444
lang: "JavaScript",
4545
description: "hoge",
4646
pages: [
47-
{id: 1, title: "JavaScriptへようこそ"},
48-
{id: 2, title: "基本構文とデータ型"},
49-
{id: 3, title: "制御構文"},
50-
{id: 4, title: "関数とクロージャ"},
51-
{id: 5, title: "'this'の正体"},
52-
{id: 6, title: "オブジェクトとプロトタイプ"},
53-
{id: 7, title: "クラス構文"},
54-
{id: 8, title: "配列とイテレーション"},
55-
]
47+
{ id: 1, title: "JavaScriptへようこそ" },
48+
{ id: 2, title: "基本構文とデータ型" },
49+
{ id: 3, title: "制御構文" },
50+
{ id: 4, title: "関数とクロージャ" },
51+
{ id: 5, title: "'this'の正体" },
52+
{ id: 6, title: "オブジェクトとプロトタイプ" },
53+
{ id: 7, title: "クラス構文" },
54+
{ id: 8, title: "配列とイテレーション" },
55+
{ id: 9, title: "非同期処理①: Promise" },
56+
{ id: 10, title: "非同期処理②: Async/Await" },
57+
],
5658
},
5759
{
5860
id: "cpp",

app/terminal/worker/jsEval.worker.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ async function runCode({ id, payload }: WorkerRequest["runCode"]) {
4646
code = "var " + code.trim().slice(4);
4747
}
4848
// eval()の中でclassを作成した場合も同様
49-
const classRegExp = /^\s*class\s+([A-Za-z0-9_]+)/;
49+
const classRegExp = /^\s*class\s+(\w+)/;
5050
if (classRegExp.test(code)) {
5151
code = code.replace(classRegExp, "var $1 = class $1");
5252
}
@@ -63,6 +63,9 @@ async function runCode({ id, payload }: WorkerRequest["runCode"]) {
6363
throw e;
6464
}
6565
}
66+
} else if (/^\s*await\W/.test(code)) {
67+
// promiseをawaitする場合は、promiseの部分だけをevalし、それを外からawaitする
68+
result = await self.eval(code.trim().slice(5));
6669
} else {
6770
// Execute code directly with eval in the worker global scope
6871
// This will preserve variables across calls

public/docs/javascript-10.md

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
# 第10章: 非同期処理(2)- Async/Await と Fetch API
2+
3+
前回(第9章)では、JavaScriptの非同期処理の要である `Promise` について学びました。しかし、`.then()` チェーンが長く続くと、コードの可読性が下がる(いわゆる「コールバック地獄」に近い状態になる)ことがあります。
4+
5+
第10章では、この課題を解決するために導入された **Async/Await** 構文と、現代的なHTTP通信の標準である **Fetch API** について解説します。他の言語で同期的なコード(ブロッキング処理)に慣れ親しんだ方にとって、Async/Await は非常に直感的で扱いやすい機能です。
6+
7+
## Async/Await 構文
8+
9+
`async``await` は、ES2017で導入された `Promise`**シンタックスシュガー(糖衣構文)**です。これを使うことで、非同期処理をあたかも「同期処理」のように上から下へと流れるコードとして記述できます。
10+
11+
### `async` 関数
12+
13+
関数宣言の前に `async` キーワードを付けると、その関数は自動的に **Promiseを返す** ようになります。値を `return` した場合、それは `Promise.resolve(値)` と同じ意味になります。
14+
15+
```js-repl:1
16+
> async function getMessage() { return "Hello, Async!"; }
17+
undefined
18+
> // async関数は常にPromiseを返す
19+
> getMessage()
20+
Promise { 'Hello, Async!' }
21+
22+
> // 通常のPromiseと同じくthenで値を取り出せる
23+
> getMessage().then(v => console.log(v))
24+
Promise { <pending> }
25+
Hello, Async!
26+
```
27+
28+
### `await`
29+
30+
`async` 関数の内部(またはモジュールのトップレベル)でのみ使用できるキーワードです。
31+
`await` は、右側の Promise が **Settled(解決または拒否)されるまで関数の実行を一時停止** します。Promiseが解決されると、その結果の値を返して実行を再開します。
32+
33+
これは、C\#`async/await` や Python の `asyncio` に慣れている方にはおなじみの挙動でしょう。
34+
35+
```js-repl:2
36+
> function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }
37+
undefined
38+
> async function run() {
39+
... console.log("Start");
40+
... await delay(1000); // 1秒待機(ここで実行が一時停止)
41+
... console.log("End");
42+
... }
43+
undefined
44+
> run()
45+
Promise { <pending> }
46+
// (1秒後に表示)
47+
Start
48+
End
49+
```
50+
51+
## try...catch によるエラーハンドリング
52+
53+
生の `Promise` では `.catch()` メソッドを使ってエラーを処理しましたが、Async/Await では、他の言語と同様に標準的な `try...catch` 構文を使用できます。これにより、同期エラーと非同期エラーを同じ構文で扱えるようになります。
54+
55+
```js:async_try_catch.js
56+
// ランダムに成功・失敗する非同期関数
57+
function randomRequest() {
58+
return new Promise((resolve, reject) => {
59+
setTimeout(() => {
60+
const success = Math.random() > 0.5;
61+
if (success) {
62+
resolve("Success: データ取得完了");
63+
} else {
64+
reject(new Error("Failure: サーバーエラー"));
65+
}
66+
}, 500);
67+
});
68+
}
69+
70+
async function main() {
71+
console.log("処理開始...");
72+
try {
73+
// awaitしているPromiseがrejectされると、例外がスローされる
74+
const result = await randomRequest();
75+
console.log(result);
76+
} catch (error) {
77+
// ここでエラーを捕捉
78+
console.error("エラーが発生しました:", error.message);
79+
} finally {
80+
console.log("処理終了");
81+
}
82+
}
83+
84+
main();
85+
```
86+
87+
```js-exec:async_try_catch.js
88+
処理開始...
89+
エラーが発生しました: Failure: サーバーエラー
90+
処理終了
91+
```
92+
93+
*(※注: 実行結果はランダムで成功する場合もあります)*
94+
95+
## Fetch API によるHTTPリクエスト
96+
97+
JavaScript(特にブラウザ環境や最近のNode.js)でHTTPリクエストを行うための標準APIが `fetch` です。以前は `XMLHttpRequest` という扱いづらいAPIが使われていましたが、現在は `fetch` が主流です。
98+
99+
`fetch` 関数は `Promise` を返します。
100+
101+
基本的な流れは以下の通りです:
102+
103+
1. `fetch(url)` を実行し、レスポンスヘッダーが届くのを待つ。
104+
2. Responseオブジェクトを受け取る。
105+
3. Responseオブジェクトからメソッド(`.json()`, `.text()`など)を使ってボディを読み込む(これも非同期)。
106+
107+
```js:fetch_basic.js
108+
// 外部APIからJSONデータを取得する例
109+
// (Node.js 18以上ではfetchが標準で使用可能です)
110+
111+
async function getUserData(userId) {
112+
const url = `https://jsonplaceholder.typicode.com/users/${userId}`;
113+
114+
try {
115+
// 1. リクエスト送信 (ネットワークエラー以外はrejectされない)
116+
const response = await fetch(url);
117+
118+
// 2. HTTPステータスコードの確認
119+
if (!response.ok) {
120+
throw new Error(`HTTP Error: ${response.status}`);
121+
}
122+
123+
// 3. レスポンスボディをJSONとしてパース (これもPromiseを返す)
124+
const data = await response.json();
125+
126+
console.log(`Name: ${data.name}`);
127+
console.log(`Email: ${data.email}`);
128+
129+
} catch (error) {
130+
console.error("Fetch failed:", error.message);
131+
}
132+
}
133+
134+
getUserData(1);
135+
```
136+
137+
```js-exec:fetch_basic.js
138+
Name: Leanne Graham
139+
Email: Sincere@april.biz
140+
```
141+
142+
### JSONデータの送信 (POST)
143+
144+
データを送信する場合は、第2引数にオプションオブジェクトを渡します。
145+
146+
```js-repl:3
147+
> const postData = { title: 'foo', body: 'bar', userId: 1 };
148+
> await fetch('https://jsonplaceholder.typicode.com/posts', {
149+
... method: 'POST',
150+
... headers: { 'Content-Type': 'application/json' },
151+
... body: JSON.stringify(postData)
152+
... }).then(res => res.json())
153+
{ title: 'foo', body: 'bar', userId: 1, id: 101 }
154+
```
155+
156+
## Promise.all() と Promise.race()
157+
158+
Async/Await は便利ですが、単純に `await` を連発すると、処理が**直列(シーケンシャル)**になってしまい、パフォーマンスが落ちる場合があります。複数の独立した非同期処理を行う場合は、並列実行を検討します。
159+
160+
### 直列実行(遅いパターン)
161+
162+
```javascript
163+
// Aが終わってからBを開始する
164+
const user = await fetchUser();
165+
const posts = await fetchPosts();
166+
```
167+
168+
### Promise.all() による並列実行
169+
170+
複数のPromiseを配列として受け取り、**全て完了するのを待って**から結果の配列を返します。一つでも失敗すると全体が失敗(reject)します。
171+
172+
```js:promise_all.js
173+
const wait = (ms, value) => new Promise(r => setTimeout(() => r(value), ms));
174+
175+
async function parallelDemo() {
176+
console.time("Total Time");
177+
178+
// 2つの処理を同時に開始
179+
const p1 = wait(1000, "User Data");
180+
const p2 = wait(1000, "Post Data");
181+
182+
try {
183+
// 両方の完了を待つ
184+
const [user, post] = await Promise.all([p1, p2]);
185+
console.log("Result:", user, "&", post);
186+
} catch (e) {
187+
console.error(e);
188+
}
189+
190+
// 本来なら直列だと2秒かかるが、並列なので約1秒で終わる
191+
console.timeEnd("Total Time");
192+
}
193+
194+
parallelDemo();
195+
```
196+
197+
```js-exec:promise_all.js
198+
Result: User Data & Post Data
199+
Total Time: 1.008s
200+
```
201+
202+
### Promise.race()
203+
204+
複数のPromiseのうち、**最も早く完了(または失敗)したもの**の結果だけを返します。タイムアウト処理の実装などによく使われます。
205+
206+
```js-repl:4
207+
> const fast = new Promise(r => setTimeout(() => r("Fast"), 100));
208+
> const slow = new Promise(r => setTimeout(() => r("Slow"), 500));
209+
> await Promise.race([fast, slow])
210+
'Fast'
211+
```
212+
213+
## この章のまとめ
214+
215+
* **Async/Await**: `Promise` をベースにした糖衣構文。非同期処理を同期処理のように記述でき、可読性が高い。
216+
* **Error Handling**: 同期コードと同じく `try...catch` が使用可能。
217+
* **Fetch API**: モダンなHTTP通信API。`response.ok` でステータスを確認し、`response.json()` でボディをパースする2段構えが必要。
218+
* **並列処理**: 独立した複数の非同期処理は `await` を連続させるのではなく、`Promise.all()` を使用して並列化することでパフォーマンスを向上させる。
219+
220+
## 練習問題
221+
222+
### 問題1: ユーザー情報の取得と表示
223+
224+
以下の要件を満たす関数 `displayUserSummary(userId)` を作成してください。
225+
226+
1. `https://jsonplaceholder.typicode.com/users/{userId}` からユーザー情報を取得する。
227+
2. `https://jsonplaceholder.typicode.com/users/{userId}/todos` からそのユーザーのTODOリストを取得する。
228+
3. 上記2つのリクエストは、**パフォーマンスを考慮して並列に実行**すること。
229+
4. 取得したデータから、「ユーザー名」と「完了済み(completed: true)のTODOの数」を出力する。
230+
5. 通信エラー時は適切にエラーメッセージを表示する。
231+
232+
```js:practice10_1.js
233+
```
234+
235+
```js-exec:practice10_1.js
236+
```
237+
238+
### 問題2: タイムアウト付きFetch
239+
240+
指定したURLからデータを取得するが、一定時間内にレスポンスが返ってこない場合は「タイムアウト」としてエラーにする関数 `fetchWithTimeout(url, ms)` を作成してください。
241+
*ヒント: `fetch` のPromiseと、指定時間後に reject するPromiseを `Promise.race()` で競走させてください。*
242+
243+
```js:practice10_2.js
244+
```
245+
246+
```js-exec:practice10_2.js
247+
```

0 commit comments

Comments
 (0)