Promise・Observable・async/await で同じ事をやってみて違いを理解しよう

`Promise/then`・`Observable`・`async/await`の3者って、似たような文脈でよく出てくるけど、出くわすたびに**なんとなく書いてなんとかしてた**感があったので、

同じ動作をするコードを書いてみて、書き方にどんな違いが出てくるか比較してみよう、という趣旨の記事です。

きっと理解が深まると思います。

3者の関係を説明しておくと、

 - `Promise`の扱い方の中に、`then`を使う方法と`async/await`を使う方法がある
 - `Observable`の扱いは、`Promise`の「`then`を使う方法」と似ている

という感じです。

`then`と`async/await`はどちらも`Promise`を扱う方法で、`Promise`と`Observable`は似ていますが違うものです。

# (2020-04-18)改定のお知らせ
筆者の理解が深まったため、記事を全面改訂しました。

 - 「`async`の場合」に、`async`を使えるのに使っていなかったところで`async`を使用した。
 - エラーハンドリングのコードを、3パターンでちゃんと同じ動作をするようにした。
 - それに伴い解説文もいろいろ変更

# 環境

node v8.9.4
rxjs v6.5.5

バージョン的な留意点としては、
nodeでは未だにES Moduleがデフォルトで機能しないということと
rxjsはoperatorを全部`pipe()`の中に入れる書き方になった後のバージョンだっていう
そのあたりですかね。



# 通常処理

## どんなことやらせるの？

AはBを呼んでその結果を使う処理。

BはCを呼んでその結果を使う処理です。

AはBの結果を待ち、BはCの結果を待つわけですが、

Cは、時間がかかったり、エラーになる可能性がある処理です。

そんな感じ。

処理の発生はA→B→Cの順ですが、完了はC→B→Aの順になっておりまして、

**コード内のコメントは下から読んだほうがわかりやすいです。**

## `Promise/then`の場合

ではコードを見ていきましょう。

```javascript:a_promise.js
// functionBの結果を待って、?????を付けて画面に出力
const functionA = () => {
  functionB().then((x) => console.log(x + "?????"));
};

// functionCの結果を待って、中身の末尾に!!!!!を付けた、新しいPromiseをreturn
const functionB = () => {
  return functionC().then((x) => x + "!!!!!");
};

// 1秒待機して、OH YEAHをresolveする(Promiseに包んでreturnする)
const functionC = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve("OH YEAH"), 1000);
  });
};

functionA();

```

コードの最後に`functionA()`を呼び出して全体を実行しています。

ではこのコードを実行してみましょう。

```
$ node a_promise.js
```

```
OH YEAH!!!!!?????
```

1秒の待機の後、こちらが出力されました。

`functionB`と`functionA`による修飾(!!!!!と?????を付け足す)がちゃんと機能してますね。

見ておきたいポイントは`functionB`です。

一度`then`で値を取り出した後、`then`に渡した関数の中で`x+"!!!!!"`を`return`しています。

(アロー関数になっていて`return`という文字はないですが、`=>`の右側がreturnされる中身です。)

そしてその`return`されたモノを中身として持つ**Promiseが、`then`の返り値になります**。

`return`された中身を改めてPromiseで包んで、全体(functionB)の返り値にしてるというイメージです。


では次。

## `Observable`の場合

コードを見てみます。

*注意:nodeで実行するための記述がいくつかあります。本筋からそれるのでここでは解説を省略します。*
 - *ファイル名の拡張子が`.mjs`なこと*
 - *import文*
 - *実行コマンドのオプション`--experimental-modules`*
 - *出力の一行目*

*`npm install rxjs`は済ませてあります。*

```javascript:a_observable.mjs
import rxjs from "rxjs";
const { Observable } = rxjs;
import operators from "rxjs/operators";
const { map } = operators;

// functionBの結果を待って、?????を付けて画面に出力
const functionA = () => {
  functionB().subscribe((x) => console.log(x + "?????"));
};

// functionCの結果を待って、Observableの中身に!!!!!をつける改造を施してreturn
const functionB = () => {
  return functionC().pipe(map((x) => x + "!!!!!"));
};

// 1秒待機して、observer.nextを呼ぶ(OH YEAHをObservableに包んでreturnする)
const functionC = () => {
  return Observable.create((observer) => {
    setTimeout(() => observer.next("OH YEAH"), 1000);
  });
};

functionA();

```

実行してみます。


```
$ node --experimental-modules a_observable.mjs
```


```
(node:17813) ExperimentalWarning: The ESM module loader is experimental.
OH YEAH!!!!!?????
```

1秒の待機の後、こちらが出力されました。(関係ない一行目を無視すると)さっきと全く同じですね。

コードを見ると、


`functionB`の中身が、`.then()`じゃなくて`.pipe(map(())`になってますね。

`then`は、Promiseの中身を受け取る時に使うメソッドですが、`pipe`はObservableの中身を受け取る時に使うメソッドではありません。

Observableの中身を受け取るときは`subscribe`を使いますからね。


`Promise/then`の場合と比べると、`then`が一度Promiseの中身を受け取るのに対して、`pipe`はObservableの外から中身にだけ改造を施すようなイメージです。その改造に使うツールをoperatorと言って、`pipe`の引数に渡して使います。

`map(x=>x+"!!!!!")`は、「来た`x`を`x+"!!!!!"`に改造する」というoperatorです。

で、`pipe`の返り値はまた`Observable`になります。

ちなみに`pipe`の中には複数のoperatorを入れることができ、書いた順に適用されます。


## `async/await`の場合

```javascript:a_async.js
// functionBの結果を待って、?????を付けて画面に出力
const functionA = async () => {
  console.log((await functionB()) + "?????");
};

// functionCの結果を待って、中身の末尾に!!!!!を付けた、新しいPromiseをreturn
const functionB = async () => {
  return (await functionC()) + "!!!!!";
};

// 1秒待機して、OH YEAHをresolveする(Promiseに包んでreturnする)
const functionC = async () => {
  return await new Promise((resolve, reject) => {
    setTimeout(() => resolve("OH YEAH"), 1000);
  });
};

functionA();

```

実行します。

```
$ node a_async.js
```

```
OH YEAH!!!!!?????
```

1秒の待機の後、こちらが出力されました。こちらも全く同じです。

コードの見た目が結構変わりましたね。





`async`関数は、**`return`された値を`Promise`に包んで返します。**

`Promise`で包むということは、その中身は最初から判明してなくてもいいわけなので、その中で「何かを待ってから値を返す」という処理を書くことができるようになります。それが`await`です。

`await functionC()`のように書くと、これは**`functionC()`が返す`Promise`の中身(`resolve`した値)を表します**。

中身が判明していなければ、判明するまで待機します。

今回の`functionB`の動きを確認すると、

`functionC`の中身が判明するのを待ってから、中身に`"!!!!!"`を付け足しています。

それを用いて、先に`functionA`に渡していた`Promise`の中身を判明させる、という動きをしているわけです。

`functionC`の動きも一見わかりにくいので説明すると、

`new Promise`で一秒後に解決する`Promise`を作り、`await`でその中身を(解決を待ってから)取り出し、`return`で返すのですが、`async`なのでまた`Promise`に包まれます。

結局、`functionC`の動きはこれまでの例と変わっていません。

# エラーハンドリング

エラーハンドリングの書き方の違いも確認しておきましょう。

## どんなことやらせるの？

先程のコードの機能は残したまま、`functionC`がエラーを送出するようにして、それを`functionB`でハンドリングします。

`functionB`はエラーを画面に出力したあと、替わりの文字列(エラーではなく！)を`functionA`に返します。

`functionA`にもエラーハンドリング機能がついていますが、こちらは使用されません。

`functionC`が普通の文字列を返す場合、コード全体が先程と全く同じ動きをします。


## `Promise/then`の場合のエラーハンドリング

```javascript:e_promise.js
// functionB()でエラーハンドリングされていない場合は、ここでエラーハンドリングされる
const functionA = () => {
  functionB()
    .then((x) => console.log(x + "?????"))
    .catch((e) => console.log(e));
};

// ここでエラーハンドリング。エラーを画面出力し、
// 替わりに文字列"There was an error."に"!!!!!"を付けて送り返す。
const functionB = () => {
  return functionC()
    .catch((e) => {
      console.log(e);
      return "There was an error.";
    })
    .then((x) => x + "!!!!!");
};

// 1秒待機してrejectする(エラーの発生)
const functionC = () => {
  return new Promise((resolve, reject) => {
    // setTimeout(() => resolve("OH YEAH"), 1000);
    setTimeout(() => reject("THIS IS THE ERROR"), 1000);
  });
};

functionA();

```


```
$ node e_promise.js
```

```
THIS IS THE ERROR
There was an error.!!!!!?????
```

1秒の待機のあと、不思議な出力が出ました。

`functionC`が発生させたエラーは`functionB`の中でキャッチされ、コンソールに出力されています。

その後`functionB`は、エラーを`"There was an error."`という正常な文字列で置き換え、通常通り`"!!!!!"`を付けて返すので、

`functionA`はそれにさらに`"?????"`を付けて出力しています。

`functionA`のエラーハンドリング機能は使われません。




## `Observable`の場合のエラーハンドリング


```javascript:e_observable.mjs
import rxjs from "rxjs";
const { Observable } = rxjs;
import operators from "rxjs/operators";
const { map, catchError } = operators;

// functionB()でエラーハンドリングされていない場合は、ここでエラーハンドリングされる
// subscribe()の二つ目の引数は、エラーをハンドリングする関数。
const functionA = () => {
  functionB().subscribe(
    (x) => console.log(x + "?????"),
    (e) => console.log(e)
  );
};

// ここでエラーハンドリング。エラーを画面出力し、
// 替わりに文字列"There was an error."に"!!!!!"を付けて送り返す。
const functionB = () => {
  return functionC().pipe(
    catchError((e) => {
      console.log(e);
      return ["There was an error."];
    }),
    map((x) => x + "!!!!!")
  );
};

// 1秒待機してobserver.errorを呼ぶ(エラーの発生)
const functionC = () => {
  return Observable.create((observer) => {
    // setTimeout(() => observer.next("OH YEAH"), 1000);
    setTimeout(() => observer.error("THIS IS THE ERROR"), 1000);
  });
};

functionA();

```

```
$ node --experimental-modules e_observable.mjs
```

```
(node:46592) ExperimentalWarning: The ESM module loader is experimental.
THIS IS THE ERROR
There was an error.!!!!!?????
```

一行目を除けば、`Promise/then`の場合と同じ出力です。

`functionB`内の`functionC().pipe(...)`の中で,
`map`より先に`catchError`という処理をつけることでエラーハンドリングしています。

`functionC`の返り値がエラーじゃない場合は、この`catchError`は無視されます。

`catchError`の返り値の文字列が`[]`に入っているのは、こうしないと文字が一文字ずつ送出されてしまうためです。




## `async/await`の場合のエラーハンドリング


```javascript:e_async.js
// functionB()でエラーハンドリングされていない場合は、ここでエラーハンドリングされる
const functionA = async () => {
  console.log((await functionB().catch((e) => console.log(e))) + "?????");
};

// ここでエラーハンドリング。エラーを画面出力し、
// 替わりに文字列"There was an error."に"!!!!!"を付けて送り返す。
const functionB = async () => {
  return (
    (await functionC().catch((e) => {
      console.log(e);
      return "There was an error.";
    })) + "!!!!!"
  );
};

// 1秒待機してrejectする(エラーの発生)
const functionC = async () => {
  return await new Promise((resolve, reject) => {
    // setTimeout(() => resolve("OH YEAH"), 1000);
    setTimeout(() => reject("THIS IS THE ERROR"), 1000);
  });
};

functionA();

```

```
$ node e_async.js
```

```
THIS IS THE ERROR
There was an error.!!!!!?????
```

`Promise/then`の場合と同様に、`functionC().catch(...)`という構文でエラーハンドリングをします。

`functionC()`の返り値が正常な値なら`.catch`は無視され、エラーなら`.catch`により正常な中身で置き換えられるので、その中身を`await`で取り出すことができます。

その後`"!!!!!"`を付け、`functionA`に返します。



# 結論、3者で何が違った？

では最後に、改めて、3者で何が違ったか確認して、この記事を終わっておきましょう。

次の点に注目して確認すると良いと思います。

 - `then`の場合と`Observable`の場合で書き方が似ていること
 - `async/await`を使って書き方を簡略化できていること

## PromiseとObservableの作り方



```javascript

// Promise(普通に)
const functionC = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve("OH YEAH"), 1000);
  });
};

// Observable
const functionC = () => {
  return Observable.create((observer) => {
    setTimeout(() => observer.next("OH YEAH"), 1000);
  });
};

// Promise(async/awaitで)
const functionC = async () => {
  return await new Promise((resolve, reject) => {
    setTimeout(() => resolve("OH YEAH"), 1000);
  });
};

```

Promiseを作る場合は、コンストラクタに「(resolve,reject)という2つの関数のセットを受け取る関数」を渡します。
Observableを作る場合は、createメソッドに「observerを受け取る関数」を渡します。observerは`next``error``complete`という3つの関数を持つオブジェクトです。
参考→[RxJS基礎中の基礎](https://qiita.com/agajo/items/7942743a0130f7a0f30b)

要は、いくつかの関数を受け取る関数を渡せば良いわけですね。

Promiseで言うところの`resolve`と、Observableで言うところの`next`または`complete`が似たような働きをします。

エラーの場合は`reject`または`error`を使います。


ただ、PromiseやObservableって、自分で作るケースよりも、何かしらのライブラリが返してきてそれを使うケースの方が多いので、作り方の違いはあんまり重要じゃないかもしれません。

あと、このケースだけ、`async/await`を使った結果、書き方が冗長になってます。結果を使う方ではちゃんと簡略化されてますのでお許しください。

## 結果の受け取り方

```javascript

// Promise/then
  functionB().then((x) => console.log(x + "?????"));

//Observable
  functionB().subscribe((x) => console.log(x + "?????"));

// async/await
  console.log((await functionB()) + "?????");

```

`Promise`は`then`メソッドに、`Observable`は`subscribe`メソッドに、中身を受け取る関数を渡すことができます。

さらに、`Promise`の場合は`await functionB()`という書き方を中身そのものとして扱うことができます。


## 中身をいじって次にわたす方法

```javascript

// Promise/then
  return functionC().then((x) => x + "!!!!!");

// Observable
  return functionC().pipe(map((x) => x + "!!!!!"));

// async/await
  return (await functionC()) + "!!!!!";

````

`Observable`で中身を改造するには、`pipe`にoperatorを渡せばよいです。この場合`subscribe`は使いません。


## エラーハンドリング

```javascript

// then/catch
  return functionC()
    .catch((e) => {
      console.log(e);
      return "There was an error.";
    })
    .then((x) => x + "!!!!!");

// Observable
  return functionC().pipe(
    catchError((e) => {
      console.log(e);
      return ["There was an error."];
    }),
    map((x) => x + "!!!!!")
  );

// async/await
  return (
    (await functionC().catch((e) => {
      console.log(e);
      return "There was an error.";
    })) + "!!!!!"
  );

````

`Promise`の場合は、`.catch`を付けてエラーハンドリングします。`.catch`は、エラーじゃない場合は無視され、コードの動きに影響しません。

`Observable`の場合は、`pipe`の中に`catchError`というオペレーターを加えます。これもエラーじゃない場合は無視されます。


# おわり

こんな所ですかね。

ちなみにこの記事では触れてませんが、Promiseの場合のエラーハンドリング関数は、Observablelの`.subscribe()`の時と同じように`.then()`の第二引数に渡してもいいです。

# 宣伝

この記事を読んでいる人のほとんどは、日々作業を抱えている人だと思います。

みんなで、サボらずに、メリハリをつけて作業に取り組むためのwebサービスをReact+NextJSで作成しております。

[みんなで作業場](https://sagyoba.oka-ryunoske.work/)

ぜひ~~遊びに~~作業しに来てください！