RxJS中具有延时和抗闪烁的加载指示 [英] Loading indication with a delay and anti-flickering in RxJS

查看:11
本文介绍了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>;

现在,要解决第一个问题

如果数据早于1秒成功到达,则不应显示任何指示器(且数据应正常呈现)

我们可以将timer设置为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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆