RxJS中具有延时和抗闪烁的加载指示 [英] Loading indication with a delay and anti-flickering in RxJS
问题描述
我想使用RxJS(版本6)实现加载指示。在异步数据调用完成之前,组件中会显示一个加载指示符(微调)。我有一些规则要执行(这些规则是否正确可能是另一个问题,也许请留下评论):
- 如果数据早于1秒成功到达,则不应显示任何指示器(数据应正常呈现)
- 如果调用早于1秒失败,则不应显示任何指示(并显示错误消息)
- 如果数据到达晚于1秒,则应至少显示1秒的指示器(为防止微调控件闪烁,应在稍后渲染数据)
- 如果呼叫失败晚于1秒,则应至少显示1秒的指示器
- 如果呼叫超过10秒,则应取消呼叫(并显示错误消息)
我在一个角度项目中实现此功能,但我认为这不是角度特定的。
我找到了此拼图的一些碎片,但我需要帮助才能将它们组装在一起。
In this SO answer存在延迟显示加载指示符的运算符的实现。
this article中描述了一个很好但不完整的角度实现。
在this Medium article中介绍了在最短时间内显示加载指示器。
推荐答案
首先,这是一个很好的问题,卢卡斯!
前言:虽然有其他方法可以实现您的要求,但我只是想让我的答案更像一个详细的循序渐进的教程。一定要看看Brandon令人惊叹的解决方案,就在这个下面。
为方便起见,假设我们有一个执行请求并返回字符串消息的观察值的方法:
const makeARequest: () => Observable<{ msg: string }>;
现在我们可以声明将保存结果的观察值:
// Our result will be either a string message or an error
const result$: Observable<{ msg: string } | { error: string }>;
和加载指示:
// This stream will control a loading indicator visibility
// if we get a true on the stream -- we'll show a loading indicator
// on false -- we'll hide it
const loadingIndicator$: Observable<boolean>;
现在,要解决第一个问题
我们可以将timer设置为1秒,并将计时器事件转换为如果数据早于1秒成功到达,则不应显示任何指示器(且数据应正常呈现)
true
值,这意味着显示加载指示符。takeUntil
将确保如果result$
出现在1秒之前-我们不会显示加载指示器:
const showLoadingIndicator$ = timer(1000).pipe(
mapTo(true), // turn the value into `true`, meaning loading is shown
takeUntil(result$) // emit only if result$ wont emit before 1s
);
#2
虽然第一部分将由#1解决,但要显示错误消息,我们需要从源流中捕获错误并将其转换为某种如果调用早于1秒失败,则不应显示任何指示(并显示错误消息)
{ error: 'Oops' }
。catchError运算符允许我们这样做:
result$ = makeARequest().pipe(
catchError(() => {
return of({ error: 'Oops' });
})
)
您可能已经注意到,我们在两个地方使用result$
。这意味着我们将有两个可观察到的对同一请求的订阅,这将产生两个请求,这并不是我们想要的。要解决此问题,我们只需在订阅者中share观察:
result$ = makeARequest().pipe(
catchError(() => { // an error from the request will be handled here
return of({ error: 'Oops' });
}),
share()
)
#3
如果数据晚于1秒到达,指示器应显示至少1秒(为防止微调控件闪烁,应在稍后呈现数据)
首先,我们有办法打开加载指示器,但目前我们不会关闭。让我们使用result$
流上的事件作为可以隐藏加载指示符的通知。收到结果后,我们可以隐藏指示器:
// this we'll use as an off switch:
result$.pipe( mapTo(false) )
这样我们就可以merge
开关:
const showLoadingIndicator$ = merge(
// ON in 1second
timer(1000).pipe( mapTo(true), takeUntil(result$) ),
// OFF once we receive a result
result$.pipe( mapTo(false) )
)
现在我们有了加载指示器,打开和关闭,尽管我们需要消除加载指示器的闪烁,并至少显示1秒。我想,最简单的方法是OFF开关的combineLatest值和2秒timer:
const showLoadingIndicator$ = merge(
// ON in 1second
timer(1000).pipe( mapTo(true), takeUntil(result$) ),
// OFF once we receive a result, yet at least in 2s
combineLatest(result$, timer(2000)).pipe( mapTo(false) )
)
注意:如果结果是在2秒之前收到的,此方法可能会给我们提供一个冗余的关开关。我们稍后再处理这件事。
#4
如果呼叫失败的时间晚于1秒,则指示器应至少显示1秒
我们对#3的解决方案已经有了防闪存代码,在#2中,我们已经处理了Stream引发错误的情况,所以我们在这里很好。
#5
如果呼叫时间超过10秒,则应取消呼叫(并显示错误消息)
为了帮助我们取消长时间运行的请求,我们有一个timeout运算符:如果源对象在给定时间内没有发出值,它将抛出错误
result$ = makeARequest().pipe(
timeout(10000), // 10 seconds timeout for the result to come
catchError(() => { // an error from the request or timeout will be handled here
return of({ error: 'Oops' });
}),
share()
)
我们差不多完成了,只剩下一点改进。让我们以false
值开始showLoadingIndicator$
流,这表明我们在开始时没有显示Loader。并使用distinctUntilChanged
省略从关闭到关闭的冗余开关,这是我们在#3中采用的方法。
总结一下,我们取得的成果如下:
数据-lang="js"数据-隐藏="假"数据-控制台="真"数据-巴贝尔="真">const { fromEvent, timer, combineLatest, merge, throwError, of } = rxjs;
const { timeout, share, catchError, mapTo, takeUntil, startWith, distinctUntilChanged, switchMap } = rxjs.operators;
function startLoading(delayTime, shouldError){
console.log('====');
const result$ = makeARequest(delayTime, shouldError).pipe(
timeout(10000), // 10 seconds timeout for the result to come
catchError(() => { // an error from the request or timeout will be handled here
return of({ error: 'Oops' });
}),
share()
);
const showLoadingIndicator$ = merge(
// ON in 1second
timer(1000).pipe( mapTo(true), takeUntil(result$) ),
// OFF once we receive a result, yet at least in 2s
combineLatest(result$, timer(2000)).pipe( mapTo(false) )
)
.pipe(
startWith(false),
distinctUntilChanged()
);
result$.subscribe((result)=>{
if (result.error) { console.log('Error: ', result.error); }
if (result.msg) { console.log('Result: ', result.msg); }
});
showLoadingIndicator$.subscribe(isLoading =>{
console.log(isLoading ? '⏳ loading' : '🙌 free');
});
}
function makeARequest(delayTime, shouldError){
return timer(delayTime).pipe(switchMap(()=>{
return shouldError
? throwError('X')
: of({ msg: 'awesome' });
}))
}
<b>Fine requests</b>
<button
onclick="startLoading(500)"
>500ms</button>
<button
onclick="startLoading(1500)"
>1500ms</button>
<button
onclick="startLoading(3000)"
>3000ms</button>
<button
onclick="startLoading(11000)"
>11000ms</button>
<b>Error requests</b>
<button
onclick="startLoading(500, true)"
>Err 500ms</button>
<button
onclick="startLoading(1500, true)"
>Err 1500ms</button>
<button
onclick="startLoading(3000, true)"
>Err 3000ms</button>
<script src="https://unpkg.com/rxjs@6.5.2/bundles/rxjs.umd.min.js"></script>
希望这能有所帮助
这篇关于RxJS中具有延时和抗闪烁的加载指示的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!