处理用户客户端(浏览器)与后端服务器的数据交互(一般通过 RESTful API)是前端开发最基本的问题之一。在浏览器里增删改查服务器端资源时,当前最普遍采用的解决方案是基于 Ajax 的回调或 Promise 。
Fetch API 的问题
以 Fetch API 为例,想要获取一个位于http://api/v1/的资源foo(id = 123),可以生成资源 URI 如http://api/v1/foo/123,将所需的参数构造好,最后发送 HTTP 请求fetch('http://api/v1/foo/123'),结果作为 Promise返回。
对于业务单一的简单Web应用例如新闻 Feed网站,这样的调用模式足够了。但对于高响应式的复杂Web应用例如 SaaS 控制台,可能存在两个很重要的需求:
基于资源特征(URI)的缓存机制
缓存的一种解决方案是,将 Fetch API 简单封装,使得在 HTTP API 调用层可以针对每次请求的特征(比如URI、请求参数、请求方法等)进行缓存。这种方式初看思路简单,对现有系统的侵入性也不大,但实际使用上问题较多。
上述缓存方式的问题在于,客户端代码对服务器资源的调用方法过于透明,缺少资源的抽象,导致增删改操作的副作用不好控制,往往使得处理缓存的逻辑变得很复杂。举个例子,资源 foo(id=123) 可能存在于多个 RESTful 路径上,比如/foo (foo资源的列表) 和/foo/123。缓存该资源查询后,针对 foo(id=123) 的每次更新操作都需要枚举所有 URI 进行 Cache busting。
有的同学可能就说了,是不是可以在获取某个资源的时候,有缓存就立刻返回缓存的数据,同时重新调用一次 API,当数据更新时再返回新数据。这种思路在我看来是种解决方案,但HTTP返回值是个 Promise,实现起来会十分别扭。Promise 实际上是对于未来某个可能发生结果(也就是回调函数)的抽象,且结果的返回是一次性的,就导致它灵活性不足。
使用 RxJS 封装 Fetch API
下面是经过 Rxjs 封装过的 Fetch request:
使用上,针对某种特定的资源 URI 必须要有一个单例FetchDataStore来封装它的 Fetch 过程。
举一个例子,对于应用内的资源foo(id=123),我们可以这样封装它:
跟 Promise 版本相比,封装后的 Fetch datastore 有几个好处:
惰性加载的缓存
- 对 Rx 有研究的朋友应该能看出,data$在第一次被订阅时,有从cold 转为 hot Observable切换的一段时间。
可以被动的响应更新事件
还可以通过 RxJS operators 进行进一步变化数据流
- 比如和 WebSocket 进行结合,实现服务器端推送
不过,上面的解决方案还是需要用户在合适的时机调用refetch函数,保证多个观察者的数据能一直处在最新状态。
更进一步
作者粗略的扫了一下 teamabition 的基于 Rxjs 的数据同构 SDK 。跟上面简单的 Fetch API 相比,teambition SDK 需要使用者引入下面两个规则:
Model Schema: 任意资源需要有自己独立的标示方式,并且保证在不同的上下文中保持 Schema 一致。使用 Schema 通常意味着需要引入一种类型系统,目前最常见的是 TypeScript 。
资源的数据交互都是通过封装的 Model 接口进行,避免直接调用 API。
加上数据模型抽象后,就可以在大多数情况下避免主动调用API,简化 View 层逻辑。
另外要时刻谨记:系统越复杂,出问题的可能性越大。保持系统简单高效是最好的。
本文作者:灵雀云-肖鹏
点击“阅读原文”,了解灵雀云