はじめに
「リアクティブプログラミング」という言葉は、RxJS・RxJava・Reactive Streamsといったライブラリ名とセットで語られることが多く、なんとなく「非同期処理を扱う仕組み」というイメージを持たれがちです。本記事では、リアクティブプログラミングが何を指すのか、その中核にある考え方を整理します。
リアクティブプログラミングとは
リアクティブプログラミングは、データの流れ(ストリーム)と、その変化の伝播を中心に据えたプログラミングパラダイムです。値が変化した時に、その変化を購読(subscribe)している側へ自動的に通知が伝わり、それに応じた処理が実行される、という考え方が土台になります。
言い換えると、「値を取りに行く(pull)」のではなく、「値が来たら知らせてもらう(push)」という発想の転換です。
Pull型とPush型
通常の(命令的な)プログラミングでは、値が欲しいタイミングでこちらから能動的に取得しに行くPull型が基本です。
// Pull型: 欲しいタイミングで自分から値を取りに行く
const value = getValue();
console.log(value);
一方リアクティブプログラミングでは、データの発生源(Observable)を「購読」しておくと、新しい値が発生するたびに、こちら側は何もしなくても自動的に通知がPushされてきます。
// Push型: 値が来たら自動的に処理が呼ばれる(RxJSの例)
someObservable.subscribe(value => {
console.log(value);
});
ユーザーのクリック、WebSocketで届くメッセージ、センサーの値の変化など、「いつ発生するか分からない」データを扱う場面では、Pull型よりPush型の方が自然に書けることが多くあります。
Observable(観測可能なもの)とObserver(観測者)
リアクティブプログラミングを実装するライブラリ群(ReactiveX、代表的な実装がRxJS・RxJava等)では、中心的な役割をObservableとObserverという2つの概念が担います。
- Observable: 時間の経過とともに値を送出する、データの発生源。「観測可能な対象」
- Observer: Observableを購読(subscribe)し、送出された値を受け取って処理する側
Observerは、Observableから以下の3種類の通知を受け取ります。
| メソッド | 呼ばれるタイミング |
|---|---|
onNext |
新しい値が送出されるたびに呼ばれる |
onError |
エラーが発生した時に呼ばれる(以降onNextは呼ばれなくなる) |
onCompleted |
ストリームが正常に完了した時に呼ばれる(最後のonNextの後) |
配列に対してforループで1件ずつ処理するのと似た感覚で、Observableに対しては「値が来るたびに何が起きるか」をonNext側に書いておく、というイメージです。
オペレーター:ストリームを宣言的に加工する
リアクティブプログラミングのもう1つの特徴は、ストリームに対してオペレーター(演算子)を連続して適用することで、データの加工処理を宣言的に組み立てられる点です。
// クリックイベントのストリームから、
// 300ms以内の連続クリックを除外し、座標だけを取り出す例
clickStream
.pipe(
debounceTime(300),
map(event => ({ x: event.clientX, y: event.clientY }))
)
.subscribe(position => {
console.log(position);
});
配列操作のmap・filterと似た感覚のオペレーターが、「時間軸に沿って流れてくるデータ」に対しても使えるのがポイントです。if文やループを使って手続き的に「どうやって」処理するかを書く代わりに、「どんなデータの流れにしたいか」を宣言的に記述できます。
Promise/async-awaitとの違い
非同期処理というと、JavaScriptではPromiseやasync/awaitもよく使われますが、両者には明確な違いがあります。
| 観点 | Promise/async-await | Observable(リアクティブ) |
|---|---|---|
| 扱う値の数 | 1回限りの結果を1つだけ扱う | 時間の経過とともに、複数の値を継続的に扱える |
| キャンセル | 標準では扱いにくい(別途AbortController等が必要) | 購読(subscribe)を解除するだけでキャンセルできる |
| 適した用途 | APIの1回のリクエスト・レスポンスなど | クリック・スクロール・WebSocketなど継続的に発生するイベント |
「1回きりの非同期処理の結果を待つ」場合はPromiseで十分ですが、「継続的に発生し続けるイベントの流れを扱う」場合にリアクティブプログラミングの強みが活きます。
リアクティブシステムとの違い(混同されやすい用語)
「リアクティブ」という言葉は、本記事で扱うプログラミングパラダイムとしてのリアクティブプログラミングとは別に、Reactive Manifesto(リアクティブ宣言)が定義する、より大きなアーキテクチャ設計思想としてのリアクティブシステムを指す文脈でも使われます。両者は関連はしますが、指しているレイヤーが異なります。
- リアクティブプログラミング: コードの書き方・パラダイムのレベルの話(本記事のテーマ)
- リアクティブシステム: 分散システム全体の設計思想。応答性(Responsive)・耐障害性(Resilient)・弾力性(Elastic)・メッセージ駆動(Message Driven)という4つの特性を備えたシステムアーキテクチャを指す、より大きな概念
「リアクティブ」という言葉を見かけたら、コードレベルの話か、システムアーキテクチャレベルの話か、文脈を区別すると理解しやすくなります。
まとめ
| 項目 | 内容 |
|---|---|
| リアクティブプログラミング | データの流れと変化の伝播を中心に据えたプログラミングパラダイム |
| Push型 | 値の変化を購読しておくと、自動的に通知が伝わる考え方(Pull型の対比) |
| Observable/Observer | データの発生源(Observable)と、それを購読して処理する側(Observer)の関係 |
| オペレーター | ストリームに対してmap・filter等の変換を宣言的に連結できる仕組み |
| リアクティブシステムとの違い | プログラミングパラダイムの話と、分散システムのアーキテクチャ設計思想の話は別レイヤー |