非同期通信とキャッシュとプロミス

サーバとのやりとりを全てAPI経由で行うような、いわゆるシングルページアプリケーションにおいて、必要なデータをいつ取りにいくのかというところは少し悩みどころで、というのも必要になってから取りにいくのではどうしても動作がもそもそしてしまうし、とはいえユーザの入力によって結果の違うものもあれば、必要もないデータを起動時に大量に取得するのもあまりイケてないという話で、任意のタイミングで柔軟にデータ更新を行うには画面遷移とデータ通信を切り離す必要があるように思いました。

アプリ開発界隈で一般的にどんなやり方がされているのかがとても気になるんだけれども、それはそれとしてともかく画面の遷移フローと通信を切り離したい場合、サーバからFetchしたデータを一旦何かしらの形でキャッシュしておくことになりそうです。それ自体はデータ通信するmoduleがキャッシュを持っていたらそれを返し、なかったら通信して結果を返すようにすれば比較的簡単に実現できるのではと考えました。

ただ、単純に結果だけをキャッシュするだけでは問題があって、つまりまずAから通信moduleに対してリクエストがあって、moduleがデータを取得している間に、Bから更にリクエストがあった場合に、まだAの通信が解決されていないために、2回目の通信が走ってしまいます。かと言って、勿論Bもデータを欲しているわけだから、return false;なんて言って弾いてしまうわけにはいかないし、callbackの関数を保持しておいて…みたいな処理を書くのも面倒です。

Promiseが本当に素晴らしいのは、Promiseオブジェクト自身がデータや処理を内包しつつ、それが解決された後の処理を並列に複数吊り下げられることで、前述の問題を全て解決してくれます。つまり、通信結果をキャッシュするのではなく、通信の際非同期処理のハンドリングに使用するPromise自体をキャッシュしてしまう。一旦Promiseがnewされたあとは、常にそのPromiseを返すようにすることで、まずAからのリクエストで通信処理の入ったPromiseをnewして返し、更にBからリクエストがあった場合もそのまま最初に作られたPromiseを返せば、無駄なく通信が解決された瞬間に同じデータを元としてAの処理とBの処理を走らせることができ、また、そのあとにCからのリクエストがあった場合にも、そもままキャッシュされている解決済みのPromiseを返すことで無駄な通信を発生させず処理することができました。

名称未設定-1_06

もうひとつ良い点は、常に返り値がPromiseである前提で実装することで、キャッシュがある場合とない場合を意識しなくてもよいことで、更にあらかじめthen無し(あってもよいが)でただ叩いてPromiseを生成しておくだけで、それ以後A,B,Cは予め解決されたPromiseオブジェクトを受け取ることができるため、データの先読みを容易に行うことができます。

Promiseを利用する際は現状何かしらのライブラリに頼ることになるかと思いますが、対応ブラウザに問題がなければ自分の知る範囲で最もシンプルなes6-promiseを使うことにしています。