2016-02-19 promised-state の問題点
2016-02-06 / 2016-02-14 につくった promised-state の問題点や front-end における data store についての覚書をする。問題の存在は 2016-02-14 にも書いた。
promised-state は state を管理するための container 。state を変更するための関数 (state: State) => State | Promise<State>
を渡すと state を更新してくれる。この関数はすべて直列に実行される。要するに Promise chain を管理してくれるだけのもの。
import { PromisedState } from 'promised-state';
const state = { count: 1 };
const promised = new PromisedState(state);
promised.update(({ count }) => { count: count + 1 });
promised.value()
.then(state => console.log(state));
雰囲気だけなら redux と似ているけれど、同じように使うと問題を起こす。……というのが今回の問題点。
redux を見るとなぜ非同期処理を直列化してくれないのかと思うだろう。直列化しないのは single store で非同期処理を直列化すると、それにより全体が待たされてしまうからだ。
PromisedState はこの問題を抱えているので single store として使ってはいけない。たとえば bouzuya/virtual-dom-ssr はその anti-pattern だ。たとえば +1 button を new Promise(resolve => setTimeout(resolve, 3000)
すると、click した瞬間から 3s の間まったく動かなくなる。
これを回避するには single store をやめるのが簡単だ。全体に対してこの PromisedState を適用するのではなく、state の一部分に対して PromisedState を使うと良い。
偶然なのだけど、 Qiita に Rxdux をつくったという記事があって、それについて Twitter でやりとりした。それも書いておく。
Rxdux は Redux に RxJS を使いつつ、非同期対応を加えたもの。API は違うけど PromisedState を RxJS で実装した風だ。それを single store で使う前提になっている。そもそも、ぼくが PromisedState をつくったのは RxJS だと簡単にできることが Promise だと面倒だったからなので RxJS でそれが必要なのかはよく分からないのだけど……。
single store && promised-state like という時点で、rxdux は上記の問題に当たるはずなので、それについて書いた。
似たようなことやってるけど、これ全体でやると遅い処理でブロックされてえらいことになるような…… / “Redux の Reducer で非同期の状態変更を扱えるように拡張した「Rxdux」について - Qiita” https://t.co/IS5XHpUcoo
— bouzuya (@bouzuya) February 19, 2016
最終的には、全体に対して処理を直列にするのではなく、局所的に処理を直列するという似たような結論に至ったようだ。
@saneyuki_s @bouzuya Rxduxではreducerの合成ではなくAction直列化スコープごとにStoreを用意してそれを合成する方向で対処を考えました https://t.co/g7dbM7V2Mr 関数合成のシンプルさはないですが
— Shinichi Tomita (@stomita) February 20, 2016
直列化した場合、非同期処理の利点が失われてしまう。非同期に処理している間は別のことをできるならともかく redux のような single store だとそれができなくなってしまうので、致命的だろう。
promised-state や rxdux の話はともかく、front end での data store のありかたを考えている。まず大きく online と offline で難易度が大きく変わる。常時 online なら back end に任せるのは手だ。昔ながらの transaction で対応できる。それでも速度的な問題から lock 単位などで慎重になるだろう。 offline を許容するなら transaction 的な問題を含めて data の整合性を維持しなければならない。複数 client 間の競合もあり得る。
promised-state のような直列化の話を見ていると RDBMS の transaction が思い浮かぶ。あれは isolation level を持っている。速度的な問題を解決するためだ。 RDBMS には lock の単位の話もある。 table-level lock / row-level lock 。offline における競合は分散環境の問題だ。git における conflict も似たようなものだろう。
要件次第ではあるけど、難しい問題だ。
rxdux の件に関連して。処理を直列に並べたいことは確実にあるのだけど、常に直列じゃなくて良いし、データの不整合を許容することでパフォーマンスを上げたりもしたい。これって RDBMS のトランザクションやロックまんまなんだよね。そのうち JS 内に xQL 持ち込んで(ry
— bouzuya (@bouzuya) February 19, 2016