在 [基于 Observable 构建前端防腐策略] 发布后,有一些读者留言对文章中使用 Observable 构建防腐层的典型应用感到困惑,觉得代码中的例子过于简单,不少可以通过 Promise 解决,引入 RxJS Observable 会提高而非降低复杂度。
这种顾虑是完全有道理的,在 RxJS 中可以由 Promise 操作符来替代的场景还有很多,事实上,所有能由 Observable 实现的场景理论上都可以 Promise 来实现,毕竟 RxJS 是基于 JavaScript 构建,整个 Observable 的核心实现也不过只有 100 行 代码。
然而前文的例子只是用来说明防腐层的场景,而并非复杂到一定要使用防腐层的情况。实际业务中的场景不可能只有 2 个接口,3 个组件这样简单。在复杂的业务场景下,基于 Observable 构建的防腐层可以提升我们的代码开发效率,更好的抽象和封装底层接口。以下举几个项目中防腐层的实战场景 ,每个场景均附加了在线示例。
将低成功率接口组装抽象为高成功率接口
在线示例:https://stackblitz.com/edit/rxjs-stable-improvement
操作符:retry / retryWhen / delay
有些时候后端接口的成功率较低,但是前端为了保证视图层稳定,需要对这些接口的成功率进行增强。这里,我们使用 Promise 模拟一个成功率只有 50% 的接口,代码如下:
// 成功率 50% 的接口
function unstableAPI(): Promise<boolean> {
return new Promise((resolve, reject) => {
if (Math.random() < 0.5) {
resolve(true);
} else {
reject('error');
}
});
}
通过 RxJS 的 retry
操作符,我们可以很容易将 50% 成功率的接口组装为成功率 99.9% 的接口,即每次当接口失败时,自动重试最多 10 次。
function stabilizedAPI(): Promise<boolean> {
return lastValueFrom(
from(defer(() => unstableAPI())).pipe(retry(10))
);
}
image.png
实际的业务中,以上代码会导致代码短时间内多次重试,可能会导致接口雪崩。在 RxJS 中我们可以轻松实现错误回退机制,以花费更多时间的代价来获得更大的成功几率。
我们将 stabilizedAPI
的代码修改为以下代码,当发生错误时,等待 1s 后重新发起请求,最多发送 10 次。更完善的 RxJS 退避策略可以参考 Power of RxJS when using exponential backoff 一文。
function stabilizedAPI(): Promise<boolean> {
return lastValueFrom(
from(defer(() => unstableAPI())).pipe(
retryWhen((errors) => errors.pipe(delay(1000), take(10)))
)
);
}
image.png
为启动屏目单独提供加载接口
在线示例:https://stackblitz.com/edit/rxjs-minimal-response-time
操作符:forkJoin / delay
绝大部分的前端应用都会有启动屏幕,启动屏幕中可能包含广告、加载动画或者应用 logo 信息等内容,应用启动屏幕的展示时间通常由以下两个因素决定:
启动屏幕的展示时间应当由以下逻辑计算:当网络加载耗时小于页面最小展示时间时,将以页面最小展示时间为准,当大于页面最小展示时间时,将网络加载耗时为准。简化公式为:
启动屏幕展示时间 = Max(关键接口加载时间,最短加载时间)
我们使用 Promise 来模拟关键数据返回,其中网络接口延时由 setTimeout 来模拟
function initData(): Promise<{ name: string }> {
return new Promise((resolve) => {
const networkDelay = Math.random() * 3000;
setTimeout(() => {
resolve({ name: 'lucy' });
}, networkDelay);
});
}
通过 forkJoin``delay
等 operator,我们可以组装出给启动屏幕使用的最短返回时间接口
// 初始化数据,返回时间必定大于 minimalDelay ms
function initDataWithMinimalDelay(minimalDelay: number): Promise<{ name: string }> {
return lastValueFrom(
forkJoin([
from(defer(() => initData())),
of(true).pipe(delay(minimalDelay)),
]).pipe(map(([data]) => data))
);
}
在以上代码中,当 networkDelay 调用时间小于 minimalDelay 时,将以 minimalDelay 为准,当大于 minimalDelay 时,将以 networkDelay 为准。
image.png
自动选择较快的接口使用
在线示例:https://stackblitz.com/edit/rxjs-race-query
操作符:raceWith
有时相同的数据可以从后端多个接口中获取,我们使用 Promise 模拟快慢两个接口
// 快速接口
function fastAPI(): Promise<string> {
return new Promise((resolve) => {
setTimeout(() => {
resolve('fast data');
}, 1000);
});
}
// 慢速接口
function slowAPI(): Promise<string> {
return new Promise((resolve) => {
setTimeout(() => {
resolve('slow data');
}, 3000);
});
}
在实际的使用中,我们无法提前知晓接口的网络情况,通过 raceWith
操作符,我们可以对任意个接口进行封装,自动获取其中最快的那个
function getFasterOne(): Promise<string> {
return lastValueFrom(
from(defer(() => fastAPI())).pipe(raceWith(from(defer(() => slowAPI()))))
);
}
image.png
Observable 防腐层自带竞态处理功能
在线示例:https://stackblitz.com/edit/rxjs-race-condition
操作符:exhaustMap / switchMap / concatMap
接口的请求结果返回的顺序不能保证一致,这就要求我们在业务中需要对接口的竞态问题进行处理。Dan Abramov 在 useEffect 完整指南 使用了布尔值来对数据进行处理。但是如果你使用 Observable 构建了防腐层,就会有更简单的方法来处理竞态问题。
我们使用 randomuser.me
的服务与 fromFetch
operator 构建一个简单的数据层
function getData() {
return fromFetch('https://api.randomuser.me/?page=1&results=10').pipe(
map((data) => data.json())
);
}
由于防腐层 Observable 的特性,使用 Observable 与 exhaustMap
结合就可以获得与 flag 标注相同的效果,即当前一次请求未返回时,下一次请求会被直接抛弃。
fromEvent(document.getElementById('button'), 'click')
.pipe(exhaustMap(() => getData()));
image.png
我们也可以选择以最后一次请求为基准,将之前所有的请求都抛弃,在组件内直接使用 switchMap
operator 来保证请求顺序与返回数据一致,fromFetch
中内置了 AbortController
可以将过期但仍未返回的接口置为 canceled
状态。
fromEvent(document.getElementById('button'), 'click')
.pipe(switchMap(() => getData()));
image.png
image.png
将所有发出的请求排队处理,不丢弃任何一次请求,当上一次请求未返回时,下一次请求进入队列排队。
fromEvent(document.getElementById('button'), 'click')
.pipe(concatMap(() => getData()));
image.png
将高阶数据请求抽象为单个接口
在线示例:https://stackblitz.com/edit/rxjs-high-order-query
操作符:mergeMap / map / forkJoin
有些时候需要二次请求才能获得视图层的数据,例如下图中的数据可能由 getList 与 getStatus 两个接口才能完整获取。当我们需要同步渲染这些数据时,在防腐层中抽象出 getListWithStatus 会是更好的选择。
image.png
我们使用 Promise 模拟出两个接口的内容
// 模拟获取列表数据的接口
function getList(): Promise<
Array<{
name: string;
id: string;
}>
> {
return new Promise((resolve) => {
resolve([
{
name: 'John Brown',
id: '1',
},
{
name: 'Jim Green',
id: '2',
}
]);
});
}
// 模拟获取状态接口
function getStatus(id: string) {
return new Promise((resolve) => {
if (id === '2') {
resolve('old');
} else {
resolve('young');
}
});
}
通过 mergeMap
与 forkJoin
,我们可以将高阶的请求直接打平为一阶数组,获得含有 status 列表数据的接口抽象
// 抽象后含有 status 的列表数据
function getListWithStatus() {
const getList$ = from(defer(() => getList()));
const getStatus$ = (id: string) => from(defer(() => from(getStatus(id))));
const data$ = getList$.pipe(
mergeMap((list) => {
const queryList = list.map((item) =>
getStatus$(item.id).pipe(map((status) => ({ ...item, status })))
);
return forkJoin(queryList);
})
);
return lastValueFrom(data$);
}
调用 getListWithStatus 返回的数据为
[
{
"name": "John Brown",
"id": "1",
"status": "young"
},
{
"name": "Jim Green",
"id": "2",
"status": "old"
}
]
Observable 的思想更广泛的应用在于响应式编程,但是其在防腐层构建上同样可以发挥很大作用,本文给出了 实际项目中的一些相对复杂的例子,通过 Observable 防腐层的引入可以使用较少代码来实现上述复杂功能。
复杂业务的有效设计对于简单场景来说很可能是过度设计。不建议读者在没有场景的时候强行引入 Observable,工程领域实践中没有银弹,感谢大家的阅读。
本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/RXYlTwptnj_j_OMomHOdaQ
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。