非同期処理はObservable(RxJS)

筆者は2017年頃からSPA(Single Page Application)案件の開発が増えてきました。SPAではページ遷移なしにAjaxでデータを取得してコンテンツが展開していきます。それらは非同期処理で行われるわけですが、基幹技術としてObservable(RxJS)を使用するようになりました。ObservableはDOMイベント(クリック、スクロール)やタイマーイベント、通信といったプッシュ型のデータ・ソースの取扱いが可能です。

// 1秒ごとに1, 2, 3, 4,...のデータを流し、
// 流れてきた値を出力する
Observable.interval(1000)
  .subscribe(value => console.log(value));

Observableの利点は、データソースの加工が容易なこと。例えば、「データに応じて処理を分岐させる」「遅延してデータを流す」など、多くの操作が可能です。

参考までにTypeScriptとAngularでAPI通信処理を行うコードを紹介します。callApiObservableがAPI通信を行うObservablefilter()は条件にあったデータだけを流すOperatorsubscribe()メソッドは加工済みのデータを受け取るためのメソッドです。

import { HttpClient } from "@angular/common/http";
import { Observable } from "rxjs/Observable";

/**
 * とあるコンポーネント
 */
export class FooComponent {
  constructor(private http: HttpClient) {
    this.fetchData();
  }

  /**
   * APIの通信処理
   */
  fetchData() {

    // api/bar_api.phpをコールし、
    // レスポンスとしてerror_messageキーを持ちうるオブジェクトを受け取る
    const callApiObservable = this.http.get<{ error_message?: string }>("api/bar_api.php");

    // 通信成功時用購読処理
    callApiObservable
      .filter(response => Reflect.has(response, "error_message"))
      .subscribe(
        response => console.log(`error:${Reflect.get(response, "error_message")}`)
      );

    // 通信失敗時用購読処理
    callApiObservable
      .filter(response => Reflect.has(response, "error_message") === false)
      .subscribe(
        response => console.log(`success:${Reflect.get(response, "error_message")}`)
      );

  }
}

※ Angular 5.2, TypeScript 2.7, RxJS 5.5のコードです

ObservableはECMAScriptで現在仕様策定の段階にあります(参考「Observables for ECMAScript」)。まだ策定されていないとはいえ、Observable(加えてOperatorsSchedulers)の考え方は、Reactive Extensions(Rx)の一環として数年前から盛り上がっていて、他の言語ではRxSwiftRxKotlinRxJavaといったライブラリが対応しています。Ajax、非同期処理を多用するウェブコンテンツとも相性がよいため、今後はJavaScriptでも広く使われる技術と筆者は予想しています。

未対応ブラウザで最新APIを使うには

本記事で紹介したのは全てES2015以上の仕様のもので、ES2018の機能とObservableを除けばChrome、Firefox、Edge、Safariの各最新版で使えます。未対応ブラウザでもこれらの機能を使うには、PromisepadStart()などのAPIであればポリフィルを、スプレッド演算子などのシンタックスであればTypeScriptやBabelでのトランスパイルにより対応します。IE 11やAndroid 4系など案件の対応が求められる旧式ブラウザでも、最新機能が動作するようになります。

ポリフィルはいくつかありますが、筆者はよくnpmモジュールのcore-jsを使っています。今回の例ではObservable以外に対応します。npmを使ってcore-jsをインストール後、次のようにimportで読み込んで使用します。

import "core-js/es7/string";

// 文字列123の冒頭に「0」を付与する
"123".padStart(2, "0");

Observableについては、実装している現行ブラウザは存在しないので、npmモジュール「rxjs」をインストール後、importで読み込みます。

import { Observable } from "rxjs/Observable";
import 'rxjs/Rx';

Observable.interval(1000)
  .subscribe(value => console.log(value));

最新シンタックスの対応についてですが、筆者は複数人による中規模プロジェクト開発が多いので、静的型付けが行えるTypeScriptをよく使います。ポリフィルのバンドルと合わせて、モジュールバンドラーにはwebpackを採用しています。

次の記事でwebpackによるモジュールのバンドル方法や、TypeScriptやBabelの環境構築方法について紹介しているので、あわせて参照ください。

また、本記事で紹介した機能について、実装されたECMAScriptバージョンと対応方法についてまとめました。モジュールバンドラーやトランスパイラーの設定時の参考にしていただければと思います。

機能ESバージョン未対応ブラウザ向け
対応方法
includes() (文字列)ES2015ポリフィル
Math.trunc()ES2015ポリフィル
配列のスプレッド演算子(...)(※)ES2015トランスパイル
PromiseES2015ポリフィル
includes()(配列)ES2016ポリフィル
べき乗演算子(**ES2016トランスパイル
padStart()ES2017ポリフィル
await / asyncES2017ポリフィル
+トランスパイル
オブジェクトのスプレッド演算子(...ES2018(予定)ポリフィル
Observable(未実装)npmモジュール

※ TypeScriptは配列のスプレッド演算子には対応していますが、本記事で紹介した[...文字列]という記法には未対応です。

2019年に向けてのJavaScript

ES2015以降、JavaScriptのは毎年メジャーバージョンアップするようになり、新しい機能が次々に追加されています。そのアップデートに追従することで、プログラミングが効率化する、堅牢なプロジェクト作成ができるといったメリットに繋がります

ECMAScriptの仕様を策定している委員会TC39のブランチでは、ES2018以降に導入されるJavaScriptの仕様が議論されているので、興味のある方は見てみるとよいでしょう。

個人的に気になっているのは、次の機能です。

本記事では、筆者の視点から見直したJavaScriptの機能をまとめました。自分は新機能をこう使っている、この機能が便利といった意見がありましたら、是非Twitterはてなブックマークのコメントでお寄せくださいませ。