背景
一天,我正在做着自己的事情,后端跑过来跟我说,让我看个问题,有个页面一进去,同一个接口会一次性发 4~5 个请求出去,如果这个并发量再增加,会很容易触发并发告警,让我处理下。
排查
首先根据经验,出现这种情况,几乎能断定要么是一个地方循环发送了请求,要么是多个地方都发送了相同的请求。根据对业务的了解,很快排除出,是该页面的不同组件中都会用到同一份数据,这份数据是通过接口获取的,因此发送了多个相同请求。
解决办法
要解决这个问题有一个很简单的办法,就是将这个接口请求放在这些组件的公共父组件中,然后父组件将接口返回的值再逐一传给这些子组件。
这个方法是最简单的,但是如果这些组件与公共父组件之前还隔了 n 代呢,那不是得一层一层的传递数据了,再就是如果这种数据有很多,那这个公共父组件就得请求很多它本身不需要的接口和数据,因此不推荐。
因为我们项目是用的 angular 框架,先说一说我们项目本身的解决方案:
在说之前先说一下我们深度使用的一个库,RxJS。
RxJS 是一个使用可观察序列编写异步和基于事件的程序的库。
angular 与 RxJS 是有深度绑定的,在基于 angular 框架的开发中,所有与异步、事件相关的东西都可以使用 RxJS。
先看代码:
class FetchData(id) {
requestCache = {};
requestData() {
if (this.requestCache[id]) {
return new Observable((observer) => {
this.requestCache[id].push(observer);
});
} else {
this.setRequestCacheList(id);
return this.http.get('/url', {id}).pipe(
map((res) => {
const list = this.requestCache[id];
list.forEach((item) => {
item.next(res);
});
return res;
})
);
}
}
setRequestCacheList(id, observer?) {
const list = this.requestCache[id] || (this.requestCache[id] = []);
observer && list.push(observer);
}
}
在 RxJS 中,Observable 是一个可观察对象,订阅者可以通过 subscribe 方法订阅这个可观察对象。在这个 Observable 已完成了操作,它可以通过 next 方法通知它的订阅者,next 也可以传一个参数,将值一并推送给订阅者。只要这个 Observable 中没有调用 next 方法,它的订阅者就会一直处于订阅状态,等待通知。
整体思路:
- 在这个请求类中,先定义一个请求缓存队列,请求的数据与传入的 id 有关系,需要保存这个依赖;
- 在有请求到来时,先判断是否已经有相同 id 的请求了,如果有,则将这次的请求先放入 Observable 中,如果没有,则是直接发送请求到后端;
- 在实际发送请求到后端的这次请求收到相应后,将缓存队列中所有相同 id 的 Observable 都将相应结果推送给各自的消费者。
在处理异步事务上,RxJS 和 Promise 在某种程度上是类似的,上面的方式用 Promise 来实现的话如下:
class FetchData(id) {
requestCache = {};
requestData() {
if (this.requestCache[id]) {
return new Promise((resolve) => {
this.requestCache[id].push(resolve);
});
} else {
this.setRequestCacheList(id);
return this.http.get('/url', {id}).pipe(
map((res) => {
const list = this.requestCache[id];
list.forEach((resolve) => {
resolve(res);
});
return res;
})
);
}
}
setRequestCacheList(id, resolve?) {
const list = this.requestCache[id] || (this.requestCache[id] = []);
resolve && list.push(resolve);
}
}
最后
其实这种类似于公共数据的应该存放在全局状态库中,先将数据获取到存储起来,后面需要用的直接来取就行。