LeetCodeで非同期処理をマスターするぞ！（第２弾）

# 概要
この記事は、LeetCodeの[30 Days of JavaScript](https://leetcode.com/studyplan/30-days-of-javascript/)のPromises and Time（全8問）を1問ずつ解いて非同期処理をマスターするシリーズの第２弾です。

前回は[こちら](https://qiita.com/hoshi7_n/items/e0b41d012da74d28f91b)


# 問題：2621. Sleep
[2621. Sleep](https://leetcode.com/problems/sleep/description/?envType=study-plan-v2&envId=30-days-of-javascript)

`millis` (1 <= `millis` <= 1000) が与えられるので、
`millis` ミリ秒スリープする非同期関数 `sleep` を実装する

## 使用例
```typescript
let t = Date.now()
sleep(100).then(() => console.log(Date.now() - t)) // 100
```
`.then` するので `Promise` を返す必要がある 

# 必要な知識

## 非同期処理
私が説明するより、最高に素晴らしい[こちら](https://zenn.dev/estra/books/js-async-promise-chain-event-loop)を読んでください


ざっくり
1. JavaScript はシングルスレッド言語
2. なので同時に複数の処理はできないし、ある処理を行っている間は次の処理ができない（ブロッキングが起こる）
3. JavaScript で非同期処理を行いたい場合は、環境（ブラウザとか）が提供してくれている機能である非同期APIを使う

## `setTimeout`
- 指定した時間より後に指定した関数を実行する非同期API の１つ
- 呼び出された際にタイマーをスタートさせ、すぐにreturn する（ブロッキングが起こらないようにする）
- タイマーが完了したら、指定した関数を実行する


（もうちょっとちゃんと書きたかったが、非同期奥が深すぎて断念。次回に期待。）


### 使い方
```typescript
setTimeout(functionRef, delay, param1, param2, ...)
```
#### 引数
- `functionRef`：タイマーが満了時に実行するコールバック関数
- `delay`：タイマーの時間（ミリ秒、デフォルトは0）
- `param`：`functionRef` の引数

#### 返り値
タイマー識別子 `timerId`（中身は正の整数値）


## `Promise` オブジェクト
- 非同期処理のコードを人間が簡単に使えるようにできるオブジェクト（インスタンス？）
    - Promise がない時代は **コールバック地獄** （ネストが深くて複雑なコードになっていた）らしい
    - ちゃんとした有用性の説明は[こちら](https://typescript-jp.gitbook.io/deep-dive/future-javascript/promise#promiseworu)

- 3つの **State** と 2つの **Fate** をもつ

### State
Promise オブジェクトは下の 3 つのいずれかの State をとる（同時に複数の State はとれない）
- Pending(待機状態)：初期状態。成功も失敗もしていない
- Fulfilled(履行状態)：処理が成功したことを意味する
- Rejected(拒否状態)：処理が失敗したことを意味する


## `Promise()` コンストラクタ
- 主にまだ `Promise` に対応していない関数をラップするために使用
- `new` 演算子と併用して使用することで `Promise` オブジェクト(`Promise` インスタンス)を生成できる

### 使い方

```typescript
new Promise(executor)
```

#### 引数
`executor`
- 新しい `Promise` オブジェクトを構築する過程でコンストラクターによって呼び出されるコールバック関数
- return による返り値は無視される
- 引数として 2 つのコールバック関数（慣習的に `resolve` ・ `reject` ）をとる
    - `resolve`：
    `exector` を呼び出している `Promise` オブジェクトの State を **Fulfilled** にしたい場合に呼び出す
    - `reject`（省略可）：
    `exector` を呼び出している `Promise` オブジェクトの State を **Rejected** にしたい場合に呼び出す


コールバック関数：
引数として他の関数に渡され、外側の関数の中で呼び出されて、何らかのルーチンやアクションを完了させる関数

<br>

アロー関数を使って書くのをよく見る
```typescript
const promise = new Promise((resolve, reject) => {
  if (Math.random() < 0.5) {
    resolve("Promise履行時の値");
  } else {
    reject("Promise拒否時の理由");
  }
});
```

#### 返り値
`Promise` オブジェクト

### おまけ
`Promise.resolve()` （ `Promise` の静的メソッド）でも同じようなことができるらしい 

```typescript
const promise1 = Promise.resolve("Promise履行時の値");
// この２つは大体同じ
const promise2 = new Promise(resolve => resolve("Promise履行時の値"));
```
大体一緒だが厳密には違うらしいので注意

## `async`
- 関数やメソッドの前につけると、その関数・メソッドは Promise オブジェクトを返す

```typescript
async function requestAsync(): Promise<number> {
  return 1;
}
 
// requestAsyncはこれと同じ
function request(): Promise<number> {
  return new Promise((resolve) => {
    resolve(1);
  });
}
```

## `await`
- Promise オブジェクトを返すメソッド・関数の前につけると、その `promise` が`resolve` か `reject` を返すまで待機する
- `async` をつけたメソッド・関数内でしか使えない



# 実装例

## その１- 原始的な方法
最悪
```typescript
async function sleep(millis: number): Promise<void> {
    let t = Date.now()
    while( (Date.now()) - t <= millis ) {}
    return 
}
```
でも通る
（`Promise` を返す必要があるので、`async` 関数にしている）

## その２ - 文明の利器を使用する方法
`setTimeout` を使った方法

```typescript
function sleep(millis: number): Promise<void> {
    return new Promise((resolve) => setTimeout(resolve, millis));
}
```

今回は問題文で
> It can resolve any value.

と言っているので、`return` の代わりに `await` でもOK

### つまったところ１

`async` は `Promise` にラップしてくれるのであれば、

```typescript
async function sleep(millis: number): Promise<void> {
    return setTimeout(()=>{}, millis)
}
```
でもよいのでは？と思ったが、実行すると、出力が 0 になる
（つまり指定した `millis` ミリ秒のスリープが起こっていない）

<br>
以下、自分なりに考えた理由（間違ってたら教えてください）

`setTimeout` が呼び出されると
1. メインスレッド以外のところでタイマー開始
2. タイマー識別子をすぐに return し、後続の処理を行えるようにする
3. タイマーが終了したら、タスクキュー（？）にコールバック関数をエンキュー
4. イベントループがコールバック関数をメインスレッドに持ってきて実行可能

ということが起こるらしいので、単に

```typescript
return setTimeout(()=>{}, millis)
```
だと待機が起こらなさそう
（↑ の 「2. タイマー識別子をすぐに return し、後続の処理を行えるようにする」より）

`setTimeout` 自体を Promise に入れて、タイマーが終了するまで return させないようにする必要がある

### つまったところ２

```typescript
function sleep(millis: number): Promise<void> {
    return new Promise((resolve) => setTimeout(()=>{}, millis));
}
```
でも行けるのでは？？と思ったが、これだと何も出力されない
調べたら、まさに[ここ](https://zenn.dev/estra/books/js-async-promise-chain-event-loop/viewer/g-epasync-resolve-reject#promise-%E3%82%B3%E3%83%B3%E3%82%B9%E3%83%88%E3%83%A9%E3%82%AF%E3%82%BF%E9%96%A2%E6%95%B0%E5%86%85%E3%81%AE%E8%BF%94%E3%82%8A%E5%80%A4%E3%81%AF%E7%84%A1%E8%A6%96%E3%81%95%E3%82%8C%E3%82%8B)で説明されていた


- Promise コンストラクタは引数にコールバック関数 `executor` を取るが、この `executor` の`return` は無視される
- `executor` の引数である `resolve()`（と `reject()` ） に呼び出されたもののみが、Promise オブジェクトに影響を与える
- `.then()` は Promise オブジェクトの状態が Fulfilled の時に呼び出される

つまり、
上のコードだと引数の `resolve()` を呼び出していないので、
`sleep` 関数から返されるPromise オブジェクトの状態は永遠に Pending　のまま
なので `.then()` が実行されない

```typescript
function sleep(millis: number): Promise<void> {
    return new Promise((resolve) => setTimeout(resolve, millis));
}
```
にすることで、
1. `setTimeout` のタイマー完了後に `resolve()` が実行される
2. Promise オブジェクトの状態が Fulfilled になって return される
2. `.then()` に渡されて実行される

となるらしい
なるほどなるほど


# 参考
全部ちゃんと読んで理解したわけではありませんが、
今後の備忘録とちゃんと理解できますようにという願望も込めて一度でも目にしたサイトはすべて載せておきます

## 非同期処理
- [イベントループとプロミスチェーンで学ぶJavaScriptの非同期処理](https://zenn.dev/estra/books/js-async-promise-chain-event-loop)
- [非同期処理](https://typescriptbook.jp/reference/asynchronous)
- [【メモ】JSの非同期処理について学んだことまとめ](https://zenn.dev/syamozipc/articles/js_asynchronous)
- [JavaScriptの非同期処理をじっくり理解する のシリーズ](https://zenn.dev/qnighy/articles/345aa9cae02d9d)

## Promise まわり（？）
- [async/await 入門（JavaScript）](https://qiita.com/soarflat/items/1a9613e023200bbebcb3)
- [Promise TypeScript Deep Dive 日本語版](https://typescript-jp.gitbook.io/deep-dive/future-javascript/promise)

## `SetTimeout`
- [setTimeout を完璧に理解する](http://nmi.jp/2022-11-21-Understanding-setTimeout-completely)
- [setTimeout()](https://developer.mozilla.org/ja/docs/Web/API/setTimeout)
- [スケジューリング: setTimeout と setInterval](https://ja.javascript.info/settimeout-setinterval)