Promiseのメモ - その2: チェーン、例外

[Promiseのメモ - その1: resolveとthen](https://qiita.com/kazutosato/items/41639c3877a83b9ecee2) の続き。

## チェーン

thenやcatchをいくつもつないで、「1つめの処理→2つめの処理」のような流れを作ることをプロミスチェーンと言います。

次の例は、2つめのthenを追加したものです。1つめのthenのコールバック関数で戻り値を返すと、その戻り値が2つめのthenのコールバック関数の引数になります。

```javascript
function makePromise(num) {
  return new Promise((resolve, reject) => {
    if(num % 2 == 0)
      resolve('OK');
    else
      reject('NG');
  });
}

makePromise(2).then((x) => {
  console.log(`then1 callback: ${x}`);
  return x + x;
}).then((x) => {
  console.log(`then2 callback: ${x}`);
}).catch((r) => {
  console.log(`catch callback: ${r}`);
});
```

```text:結果
then1 callback: OK
then2 callback: OKOK
```

`makePromise(1)`としてrejectすると、thenは2つともスキップされて、catchに渡した関数が実行されるように見えます。

```text:結果
catch callback: NG
```

## thenとcatchが返すPromise

[MDNの説明](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then)でthenの仕様をチェックします。

> The then() method returns a Promise.

thenはPromiseオブジェクトを返します。はっきり書いていませんが、新しいPromiseオブジェクトを作って返すということです。

> - returns a value, the promise returned by then gets resolved with the returned value as its value;
> - doesn't return anything, the promise returned by then gets resolved with an undefined value;

コールバック関数が実行されると、新しいPromiseオブジェクトは履行済みになり、履行済み結果の値はコールバック関数の戻り値になります。戻り値がないときは、履行済み結果の値はundefinedになります。

次のようにチェーンをばらしてthenが返すPromiseを調べます。また、コールバック関数が実行されたあとの状態も調べます。

```javascript
let promise = makePromise(2);
console.log(promise);
let promise_then1 = promise.then((x) => {
  console.log(`then1 callback: ${x}`);
  return x + x;
});
console.log(promise_then1);
let promise_then2 = promise_then1.then((x) => {
  console.log(`then2 callback: ${x}`);
});
console.log(promise_then2);
let promise_catch = promise_then2.catch((r) => {
  console.log(`catch callback: ${r}`);
});
console.log(promise_catch);

setTimeout(() => {
  console.log(promise);
  console.log(promise_then1);
  console.log(promise_then2);
  console.log(promise_catch);
}, 1000);
```

```text:結果（chromeの場合）
Promise {<resolved>: "OK"}
Promise {<pending>}
Promise {<pending>}
Promise {<pending>}
then1 callback: OK
then2 callback: OKOK
Promise {<resolved>: "OK"}
Promise {<resolved>: "OKOK"}
Promise {<resolved>: undefined}
Promise {<resolved>: undefined}
```

makePromise関数が1つ、thenが2つ、catchが1つのPromiseオブジェクトを作っています。thenとcatchが作ったPromiseは非同期で自動的に保留中から履行済みに変わっています。

`makePromise(1)`としてrejectすると、次の結果になります。

```text:結果（chromeの場合）
Promise {<rejected>: "NG"}
Promise {<pending>}
Promise {<pending>}
Promise {<pending>}
catch callback: NG
Promise {<rejected>: "NG"}
Promise {<rejected>: "NG"}
Promise {<rejected>: "NG"}
Promise {<resolved>: undefined}
```

thenやcatchはスキップされるわけでなく、「thenで登録したコールバック関数は棄却済みのときは実行されない」「catchで登録したコールバック関数は履行済みのときは実行されない」ということです。実行されないときは、履行/棄却済み結果の値は次のPromiseオブジェクトに引き継がれます。

catchもPromiseを返すので、catchのうしろにthenをつなげることができます。

```javascript
makePromise(2).then((x) => {
  console.log(`then1 callback: ${x}`);
  return x + x;
}).catch((r) => {
  console.log(`catch callback: ${r}`);
  return r + r;
}).then((x) => {
  console.log(`then2 callback: ${x}`);
});
```

```text:結果
then1 callback: OK
then2 callback: OKOK
```

この例は`makePromise(1)`とすると、`then2 callback: NGNG` となります。

## 例外

rejectだけでなく、一般的な例外が発生したときも、catchで登録したコールバック関数が呼ばれます。コールバック関数の引数にはErrorオブジェクトが入ります。

```javascript
function makePromise(num) {
  return new Promise((resolve, reject) => {
    if(num % 2 == 0)
      resolve('OK');
    else
      foo.bar();
  });
}

makePromise(1).then((x) => {
  console.log(`then1 callback: ${x}`);
}).catch((r) => {
  console.log(`catch callback: ${r}`);
});
```

```text:結果
catch callback: ReferenceError: foo is not defined
```

コールバック関数で例外が発生したときも、次のcatchのコールバック関数が呼ばれます。

```javascript
function makePromise(num) {
  return new Promise((resolve, reject) => {
    if(num % 2 == 0)
      resolve('OK');
    else
      reject('NG');
  });
}

makePromise(2).then((x) => {
  console.log(`then1 callback: ${x}`);
  foo.bar();
}).catch((r) => {
  console.log(`catch callback: ${r}`);
});
```

```text:結果
then1 callback: OK
catch callback: ReferenceError: foo is not defined
```

## catchがない場合

executorでrejectを呼んだ場合や、executorやコールバック関数で例外が発生した場合に、catchがないと例外が発生します（nodeでは`UnhandledPromiseRejectionWarning`が出ます）。

```javascript
function makePromise(num) {
  return new Promise((resolve, reject) => {
    if(num % 2 == 0)
      resolve('OK');
    else
      reject('NG');
  });
}

makePromise(1).then((x) => {
  console.log(`then1 callback: ${x}`);
});
```

```text:結果（chromeの場合）
Uncaught (in promise) NG
```

Promiseで発生した例外をtry - catchで捕まえることはできません（asyncとawaitを使えばできます）。次の例は意味なしです。

```javascript
try {
  makePromise(1).then((x) => {
    console.log(`then1 callback: ${x}`);
  });
}
catch(e) {
  console.log(e);
}
```

## ここまでの感想

Promiseのノリは、Promiseオブジェクトをじゃんじゃん作って、使わないものはほっておく、というものです。割とゴージャスなシステムと言えます。

- [Promiseのメモ - その1: resolveとthen](https://qiita.com/kazutosato/items/41639c3877a83b9ecee2)
- [Promiseのメモ - その3: 小ネタ](https://qiita.com/kazutosato/items/b3afd079d45352e98574)
- [Promiseのメモ - その4: asyncとawait](https://qiita.com/kazutosato/items/c1c047991315128271f5)