[[Promise (JavaScript)|Promise]]の正常系と異常系はこれまでに学習しましたので、次は[[Promise (JavaScript)|Promise]]同士の並行処理について学びます。
## Reference
- [非同期処理:Promise/Async Function · JavaScript Primer \#jsprimer](https://jsprimer.net/basic/async/)
## Lesson
### 直列処理
[[Promise (JavaScript)|Promise]]を使った複数の非同期処理を直列に処理するには[[Promiseチェーン]]を使います。以下の `delay` 関数を考えてみましょう。
```js
function delay(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
```
[[Promise (JavaScript)|Promise]]を[[Promise.prototype.then (JavaScript)|then]]で繋ぎ、第1引数に指定した[[コールバック関数 (JavaScript)|コールバック関数]]の中で **新たな[[Promise (JavaScript)|Promise]]オブジェクトをreturnし、再びthenでチェーンすることにより** 4回登場した `delay(...)` はすべて直列に実行されます。
```js
delay(500)
.then(() => {
console.log("0.5秒経過");
return delay(1000);
})
.then(() => {
console.log("1.5秒経過");
return delay(1500);
})
.then(() => {
console.log("3秒経過");
});
console.log("まずはここが実行される")
```
最終的な出力結果は以下のようになります。
```console
まずはここが実行される
0.5秒経過
1.5秒経過
3秒経過
```
### 並行処理
並行処理は[[Promiseチェーン]]ではなく、**複数のPromiseをまとめて1つのPromiseにする**ことで表現します。実際にコード例を見た方が早いので見てみましょう。
```js
function delay(ms) {
return new Promise((resolve) => {
setTimeout(() => {
console.log(`${ms / 1000} 秒経過`);
resolve();
}, ms);
});
}
Promise.all([delay(3000), delay(1500), delay(500)])
.then(() => {
console.log("終了");
});
console.log("まずはここが実行される");
```
出力結果は以下のようになります。
```console
まずはここが実行される
0.5 秒経過
1.5 秒経過
3 秒経過
終了
```
**複数のPromiseをまとめて1つのPromiseにする** 処理は [[Promise.all (JavaScript)|Promise.all]] の部分です。
```js
// 3つの非同期処理 `delay` を同時に実行し、すべてが成功した場合に『成功』となるPromise
Promise.all([delay(3000), delay(1500), delay(500)])
```
まとめられて1つになった[[Promise (JavaScript)|Promise]]が成功すると、[[Promise.prototype.then (JavaScript)|then]]の中に入ります。
```js
Promise.all([delay(3000), delay(1500), delay(500)])
.then(() => {
// 3つのdelayが返却するPromiseが『すべて』成功した場合に処理される
console.log("終了");
});
```
> [!note]
>
> 複数の[[Promise (JavaScript)|Promise]]をまとめる方法は[[Promise.all (JavaScript)|Promise.all]]以外にも存在しますが、利用頻度がそこまで多くないためここでは割愛します。
## Mission 1
#😁EASY
以下のコードを `ms` の値が1000**未満**の場合は**指定された時間待機したあとに**失敗するよう書き換えてください。
```js
function delay(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
```
%%
解答例
```js
function delay(ms) {
return new Promise((resolve, reject) => {
setTimeout(() => {
ms >= 1000 ? resolve() : reject(new Error("失敗しました"));
}, ms);
});
}
```
%%
## Mission 2
#🙂NORMAL
[[#Mission 1]]で書き換えた `delay` 関数を利用した場合、以下のコードが例外処理を考慮できるように書き換えてください。ただし、**例外が発生したら後続の処理は続行しない** ようにしてください。
```js
delay(500)
.then(() => {
console.log("0.5秒経過");
return delay(1000);
})
.then(() => {
console.log("1.5秒経過");
return delay(1500);
})
.then(() => {
console.log("3秒経過");
});
console.log("まずはここが実行される")
```
%%
解答例
```js
delay(500)
.then(() => {
console.log("0.5秒経過");
return delay(1000);
})
.then(() => {
console.log("1.5秒経過");
return delay(1500);
})
.then(() => {
console.log("3秒経過");
})
.catch(console.error);
```
%%
> [!hint]- Hint 1
> [[Promise.prototype.catch (JavaScript)|Promise.prototype.catch]] を使います。
## Mission 3
#🙂NORMAL
[[#Mission 2]]で書き換えたコードについて、**例外が発生しても後続の処理を続行する** ようにしてください。
%%
解答例
```js
delay(500)
.then(() => {
console.log("0.5秒経過");
return delay(1000);
})
.catch(console.error)
.then(() => {
console.log("1.5秒経過");
return delay(1500);
})
.catch(console.error)
.then(() => {
console.log("3秒経過");
})
.catch(console.error);
```
%%
> [!hint]- Hint 1
> [[Promise.prototype.catch (JavaScript)|Promise.prototype.catch]] を差し込む場所によってどう挙動が変わるかを確認してみましょう。
## Mission 4
#🙂NORMAL
[[#Mission 1]]で **書き換える前** の `delay` 関数を使って、以下の流れで実行される処理を実現するコードを書いてください。また、**緑色背景の処理** では [[console.timeLog]] を使って経過時間を出力してください。
> [!caution]
> `delay` 関数は変更禁止です。
```mermaid
graph TD
A([開始]) --> B[1秒待つ]
B --> C{並行処理開始}
C --> D[0.5秒待つ]
C --> E[1.5秒待つ]
D --> F{並行処理完了}
E --> F
F --> G[1秒待つ]
G --> H([終了])
classDef process fill:#90CAF9,stroke:#1565C0,stroke-width:2px;
classDef start fill:#A5D6A7,stroke:#2E7D32,stroke-width:2px,shape:stadium;
classDef decision fill:#A5D6A7,stroke:#2E7D32,stroke-width:2px,shape:rhombus;
class B,D,E,G process;
class A,H start;
class C,F decision;
```
%%
解答例
```js
console.time("log");
console.timeLog("log", "開始");
delay(1000)
.then(() => {
console.timeLog("log", "並列処理開始");
return Promise.all([delay(500), delay(1500)]);
})
.then(() => {
console.timeLog("log", "並列処理終了");
return delay(1000);
})
.then(() => {
console.timeLog("log", "終了");
});
```
%%
> [!hint]- Hint 1
> [[console.timeLog]]の挙動と使い方は以下のコードで確かめてみてください。
>
> ```js
> console.time("log");
> console.timeLog("log", "開始");
> delay(500)
> .then(() => {
> console.timeLog("log", "500秒経過");
> return delay(500);
> })
> .then(() => {
> console.timeLog("log", "さらに500秒経過");
> return delay(500);
> })
> .then(() => {
> console.timeLog("log", "終了");
> });
> ```
## Mission 5
#😁EASY
以下のコードを、指定時間待機したあとにメッセージを返却するよう変更してください。
```js
function delay(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
```
以下のテストが通れば問題ありません。
```js
import { expect } from "jsr:@std/expect";
Deno.test("test", () => {
return delay(1000).then((x) => expect(x).toBe("1000ms経ちました"));
});
```
%%
解答例
```js
function delay(ms) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(`${ms}ms経ちました`);
}, ms);
});
}
```
%%
## Mission 6
#🙂NORMAL
[[#Mission 4]]で解答したコードに対し、以下の変更を加えてください。
- [[#Mission 5]] で回答した `delay` 関数に差し替える
- `delay` 関数の非同期処理が完了したあとに **Promise解決後の返却値を使って** `console.log` に内容を出力する
- [[console.timeLog]] などは削除する
> [!caution]
> `delay` 関数は変更禁止です。
%%
解答例
```js
delay(1000)
.then((msg) => {
console.log(msg);
return Promise.all([delay(500), delay(1500)]);
})
.then(([msg1, msg2]) => {
console.log(msg1, msg2);
return delay(1000);
})
.then((msg3) => {
console.log(msg3);
});
```
%%
---
*NEXT* >> [[📗TDQ-027 近代の非同期処理 async await]]