JavaScriptのPromiseについて part1

# 初めに
プロミスでは約束した書き方など、統一されたやり方で従う点がたくさんあるので、構造上の話ではなく、使い方や動きから思ったことをまとめていきたいと思います。

今回主な参考文章はこちらです。
[Promises chaining - javascript.info](https://javascript.info/promise-chaining)
[Error handling with promises - javascript.info](https://javascript.info/promise-error-handling)

[Promises, async/await](https://javascript.info/async)では各章の基礎概念と使い方にはすごく分かりやすい例を出しているので、ぜひご参考ください。（日本語訳もあります。）

もっと詳しい理論や説明は、こちらをおすすめします。
[JavaScript Promiseの本](https://azu.github.io/promises-book/#chapter1-what-is-promise)

# about `then`
## Thenable object
普通のプロミスでエラーが出ない場合は、プロミスオブジェクトは非同期処理を行い結果を`return`して、`then`でその結果を受け取って、また結果を`return`したら次の`then`の引数として受け入れます。そうやってプロミスオブジェクトは結果を受け取るたびにプロミスオブジェクトは更新される。（`promise`チェーンで結果を一つにまとめる。）

しかし前述した参考文章ではこんな例が出てきた。
（いつもnode.jsでコンソール結果を見るので`alert()`ではなく`console.log()`に調整してます。）
```jsx
class Thenable {
  constructor(num) {
    this.num = num
  }
  then(resolve, reject) {
    console.log(resolve)
    setTimeout(() => resolve(this.num * 2), 1000)
  }
}

new Promise(resolve => resolve(1))
  .then(result => {
    return new Thenable(result)
  })
  .then(result => {
    console.log(result)
  })

// [Function (anonymous)] // function() { native code }
// 2
```
最初はプロミスの`then`で実行し、結果を受け取り`new Theable()`の引数として入れて`return`する。ここから参考文章の説明を読んだうえの解釈ですが、`return`した結果では`then`メソッドがあったら再び中身の内容を実行し、この例では`console.log()`　⇒　`setTimeout()`　⇒　`resolve()`、
最後の`resolve()`は`Promise`の`resolve()`のように引数`(1 * 2)`を結果として下の`then`へ渡す。

`then`の動きとしては
`Promise`の`then`
↓
`class Thanable`の`then`（≒`Promise`）
↓
`Promise`の`then`

変に感じたのは、`class Thenable`の`then`は呼び出されてないし、`return`もないのにどうやって結果を次の`then`へ渡すの？
そもそも`class Thenable`内部の`then`は`Promise`の`then`と同じではないと感じていて、説明では`Promise`のように`resolve()`の引数が結果として下の`then`に渡せる。っていうことは`class`内部の`then`を`then`の名前を使って、実際に`Promise`のような動きをしている...かな？
したがってこのプロミスチェーンの統合する特徴を利用すれば、`class`内部で互換性のある`then`メソッドを作って結果を返させ、利用することができるそうです。

[2.3. コラム: Promiseは常に非同期?](https://azu.github.io/promises-book/#promise-is-always-async)
[2.7. コラム: thenは常に新しいpromiseオブジェクトを返す](https://azu.github.io/promises-book/#then-return-new-promise)

## Returning promises
同じくpromise chainingの例です。少し気になるところの感想メモです。
```jsx:loadScript
function loadScript(src) {
  return new Promise(resolve, reject => {
    let script = document.createElement('script')
    script.src = src

    script.onload = () => resolve(script)
    script.onerror = () => reject(new Error(`Script load error: ${src}`))

    document.head.append(script)
  })
}

loadScript('/article/promise-chaining/one.js')
  .then(script => {
    return loadScript('/article/promise-chaining/two.js')
  })
  .then(script => {
    return loadScript('/article/promise-chaining/three.js')
  })
  .then(script => {
    one()
    two()
    three()
  })
```
（これはただのデモコードです。実際に動けません。）
`then`の動きが気になります。プロミスオブジェクトは最後の`then`に統合して一つまとめるそうです。上の例では、最後の`then`で三つのソースから関数を呼び出せるのは、`then`チェーンで`script`で引数`src`を貯蔵しているのかな？`then`が`return`したのは自分の引数`script`ではなく`loadScript`の結果です。そこから見ればプロミスオブジェクトには前の結果を上書きするのではなく、新しい`src`を書き込まれ、最後の`then`に三つの`src`から関数を呼び出せることになったのではないかと。

# Error handling with promises
[こちら](https://ja.javascript.info/promise-error-handling#ref-2087)のタスク問題です。
```jsx
console.log('step 1')

new Promise(function promise(resolve, reject) {
  console.log('step 2')
  setTimeout(() => {
    throw new Error('setTimeout Whoops!')
  }, 1000)
  // throw new Error('Whoops!')
  resolve('Everything is OK')
}).catch(function catchError(err) {
  console.log(err)
}).then(function getResult(result) {
  console.log(result)
})

console.log('step 3')

// step 1
// step 2
// step 3

// Everything is OK
// Error: setTimeout Whoops! // global error
```
**JavaScriptはsingle-thread、そしてプロミスは常に非同期に処理を行います。**
JavaScriptがどうやってsingle-threadで非同期処理を実現するについて、前は少し同期と[非同期処理の話題](https://qiita.com/hu-yu/items/a712884dc8422854299c)で触れていました。
今回はコードとツールを使って全体の動きを把握してみたいと思います。

[JavaScript Visualizer 9000](https://www.jsv9000.app/?code=Ly8gdGhlbkNhbGxiYWNrSXNBc3luYy5qcwpjb25zb2xlLmxvZygiWzFdIFN5bmMgcHJvY2VzcyIpOwoKY29uc3QgcHJvbWlzZSA9IG5ldyBQcm9taXNlKHJlc29sdmUgPT4gewogIGNvbnNvbGUubG9nKCJbMl0gVGhpcyBsaW5lIGlzIFN5bmNocm9ub3VzbHkgZXhlY3V0ZWQiKTsKICByZXNvbHZlKCJSZXNvbHZlZCEiKTsKfSk7Cgpwcm9taXNlLnRoZW4odmFsdWUgPT4gewogIGNvbnNvbGUubG9nKCJbNF0gVGhpcyBsaW5lIGlzIEFzeW5jaHJvbm91c2x5IGV4ZWN1dGVkIik7CiAgY29uc29sZS5sb2coIlJlc29sdmVkIHZhbHVlOiAiLCB2YWx1ZSk7Cn0pOwoKY29uc29sZS5sb2coIlszXSBTeW5jIHByb2Nlc3MiKTsK)は非同期処理をビジュアル化したツールです。
今回デモコードの動きは[　**こちら**　](https://www.jsv9000.app/?code=Y29uc29sZS5sb2coJ3N0ZXAgMScpDQoNCm5ldyBQcm9taXNlKGZ1bmN0aW9uIHByb21pc2UocmVzb2x2ZSwgcmVqZWN0KSB7DQogIGNvbnNvbGUubG9nKCdzdGVwIDInKQ0KICBzZXRUaW1lb3V0KCgpID0%2BIHsNCiAgICB0aHJvdyBuZXcgRXJyb3IoJ3NldFRpbWVvdXQgV2hvb3BzIScpDQogIH0sIDEwMDApDQogIC8vIHRocm93IG5ldyBFcnJvcignV2hvb3BzIScpDQogIHJlc29sdmUoJ0V2ZXJ5dGhpbmcgaXMgT0snKQ0KfSkuY2F0Y2goZnVuY3Rpb24gY2F0Y2hFcnJvcihlcnIpIHsNCiAgY29uc29sZS5sb2coZXJyKQ0KfSkudGhlbihmdW5jdGlvbiBnZXRSZXN1bHQocmVzdWx0KSB7DQogIGNvbnNvbGUubG9nKHJlc3VsdCkNCn0pDQoNCmNvbnNvbGUubG9nKCdzdGVwIDMnKQ%3D%3D)ご覧ください。

全体の動きとしては、
Call Stack
`console.log('step 1')`
↓
`promise() {`
　`console.log('step 2')`
　`setTimeout((){}, 1000)`　→　(Task Queue) anonymous
↓
`console.log('step 3')`
↓
`prmise() {`
　`resolve()`　→　(Run all microtasks) calls `getResult`
`}`
↓
`getResult(result) {`
　`console.log(result)` // Everything is OK
`}`
（ここまでは主なコードが実行完了してCall Stackが綺麗になった状態で）
↓
`setTimeout((){}, 1000)`　1秒後
`throw new Error('setTimeout Whoops!')`　`anonymous`を実行 // Error: setTimeout Whoops!
（このエラーはグローバルエラーになって`promise`の`catch()`を通さずJSエンジンがコンソールします。）

自分の考えですが、非同期処理といってもsingle-threadという制限のなか、非同期関数を一度別のキューに移行して下のコードに継続させて、主要なスタックが終わったら先に内部のMicrotask Queueを終わらせて結果を出します。それからTask Queueから残りの非同期関数を順番に終わらせる、といったステップを踏んでいろんな非同期を実現していると思います。

面白いと思うのはやはり`resolve()`の動きです。自分の解釈ですが、`resolve()`が呼び出されると`then()`が連動し中の関数に引数を入れてすぐ`getResult()`が呼び出されました。（簡単な概念かもしれませんが、`resolve()`がどうやって`then`中の関数を呼び出すのがずっと気になっているので。）

補足：`setTimeout`の計測器の触発は一回しかない、`anonymous`は中身の関数に指しています。（`setTimeout`の動きに興味がある方は[　**こちら**　](https://www.jsv9000.app/?code=Y29uc29sZS5sb2coJzEnKQ0KZm9yICh2YXIgaSA9IDA7IGkgPCA1OyBpKyspIHsNCiAgY29uc29sZS5sb2coJzInKQ0KICBzZXRUaW1lb3V0KCgpID0%2BIHsNCiAgICBjb25zb2xlLmxvZyhpKQ0KICB9LCAxMDAwKQ0KfQ0KY29uc29sZS5sb2coJzMnKQ%3D%3D)ご参考になれば幸いです。）

そしてエラーだった場合、処理がどう行われるかが[　**こちら**　](https://www.jsv9000.app/?code=Y29uc29sZS5sb2coJ3N0ZXAgMScpDQoNCm5ldyBQcm9taXNlKGZ1bmN0aW9uIHByb21pc2UocmVzb2x2ZSwgcmVqZWN0KSB7DQogIGNvbnNvbGUubG9nKCdzdGVwIDInKQ0KICBzZXRUaW1lb3V0KCgpID0%2BIHsNCiAgICB0aHJvdyBuZXcgRXJyb3IoJ3NldFRpbWVvdXQgV2hvb3BzIScpDQogIH0sIDEwMDApDQogIHRocm93IG5ldyBFcnJvcignV2hvb3BzIScpDQogIC8vIHJlc29sdmUoJ0V2ZXJ5dGhpbmcgaXMgT0snKQ0KfSkuY2F0Y2goZnVuY3Rpb24gY2F0Y2hFcnJvcihlcnIpIHsNCiAgY29uc29sZS5sb2coZXJyKQ0KfSkudGhlbihmdW5jdGlvbiBnZXRSZXN1bHQocmVzdWx0KSB7DQogIGNvbnNvbGUubG9nKHJlc3VsdCkNCn0pDQoNCmNvbnNvbGUubG9nKCdzdGVwIDMnKQ%3D%3D)ご覧ください。

```jsx
console.log('step 1')

new Promise(function promise(resolve, reject) {
  console.log('step 2')
  setTimeout(() => {
    throw new Error('setTimeout Whoops!')
  }, 1000)
  throw new Error('Whoops!')
  // resolve('Everything is OK')
}).catch(function catchError(err) {
  console.log(err)
}).then(function getResult(result) {
  console.log(result)
})

console.log('step 3')

// step 1
// step 2

// step 3
// Error: Whoops!
// undefined
// Error: setTimeout Whoops! // global error
```
ここの動きは、
`console.log('step 1')`
↓
`promise() {`
　`console.log('step 2')`
　`setTimeout((){}, 1000)`　→　(Task Queue) anonymous
　`throw new Error('Whoops!')`　find error in 'promise', calls `catchError`
`}`
↓
`console.log('step 3')`
↓
`catchError(err) {`
　`console.log(err)` // Error: Whoops!
`}`
↓
`getResult(result) {`
　`console.log(result)` // undefined
`}`
↓
`setTimeout((){}, 1000)`
`throw new Error('setTimeout Whoops!')` // Error: setTimeout Whoops!

エラーの場合はさきの例と動きが違いますね。`promise()`でエラーをキャッチしたらすぐ`catch`の`catchError()`へ連動してくれたけれど、コードが下に継続していきました。
さきの例と比べると私はこれも一種のError-Firstと考えていますが、なぜなら`resolve()`のような履行した状況と違い、エラーが出た場合はすぐ止めなきゃいけないからだと思います。エラーをキャッチしてすぐ`catch()`へ移行したら中断になるけど、非同期の実現のために`catch()`の`catchError`を実行しない、まずは外側のコードを完了してから、`catchError()`実行しエラーメッセージをコンソール。それから後ろのチェーンを引き続き完了させる。

前の例では`resolve()`は明らかに`console.log('step 3')`の後実行され`getResult`を呼び出した。これが`promise()`の中にエラーを発見していないから一旦外側のコードを完了させてまた戻る証拠だと思いますが。

別にエラーの検出によって全体の動きが大した変わらないし、まず外側のコードを完了するのが非同期の核心概念だと思いますが、プロミスの中の動きが工夫がなされていると感じています。