如何保证最短运行时间

How to ensure minimum operation duration

我正在使用动态数据(基于 this example)构建 Angular Material mat-tree
当用户展开一个节点时,会显示一个进度条,同时从服务器检索子节点列表。

基本代码如下所示:

toggleNode(node: DynamicFlatNode, expand: boolean) {
  if (expand) {
    // Shows progress bar
    node.isLoading = true;

    this
      .database
      .getChildren(node.id)
      .subscribe(
        values => {
          // Create and add the descendant nodes
          ...
        },
        error => { console.error(error); },
        () => {
          // Hides progress bar
          node.isLoading = false;
        });

  } else {
    // Remove descendant nodes
    ...
  }
}

问题是如果数据检索太快,进度条只是闪烁。
我想添加一个延迟,以便进度条至少显示 250 毫秒。
我希望可见时间为 min(250ms, data retrieving time) 而不是 250 ms + data retrieving time

我目前的解决方案是使用虚拟承诺,它在数据检索之前开始并在 complete 回调中等待:

toggleNode(node: DynamicFlatNode, expand: boolean) {
  if (expand) {
    // Shows progress bar
    node.isLoading = true;

    const delay = new Promise(resolve => setTimeout(resolve, 250));
    this
      .database
      .getChildren(node.id)
      .subscribe(
        values => {
          // Create and add the descendant nodes
          ...
        },
        error => { console.error(error); },
        async () => {
          await delay;
          // Hides progress bar
          node.isLoading = false;
        });

  } else {
    // Remove descendant nodes
    ...
  }
}

有更简洁的方法吗?

您可以利用 RxJS timerforkJoin 来引入人为延迟。我还建议使用 finalize 运算符来设置 isLoading 标志。如果不是,您必须在订阅的 errorcomplete 回调中设置它。

重要提示:forkJoin 只会在所有源可观察对象完成时发出。所以你需要确保你的 observable this.database.getChildren(node.id) 完成。如果没有,您可以使用 take(1) 强制它在第一次发射后完成,假设您不需要发射流。

尝试以下方法

import { forkJoin, timer } from 'rxjs';
import { finalize, map, take } from 'rxjs/operators';

const DELAY = 250;

toggleNode(node: DynamicFlatNode, expand: boolean) {
  if (expand) {
    node.isLoading = true;                            // <-- show progress bar
    forkJoin({
      timer: timer(DELAY),
      children: this.database.getChildren(node.id),
    }).pipe(
      map(({ children }) => children),                // <-- ignore the `timer` emission
      finalize(() => (node.isLoading = false))        // <-- hide progress bar
    ).subscribe({
      next: (values: any) => {
        // create and add the descendant nodes
      },
      error: (error: any) => {
        // handle error
      },
    });
  } else {
    // remove descendant nodes
  }
}

工作示例:Stackblitz。使用 DELAY 变量来观察引起的延迟。

您可以从 here

获取想法
import { of, map, Observable, combineLatest, delay, timer } from 'rxjs';
import { mapTo, startWith, tap } from 'rxjs/operators';

const GET_DATA_DELAY = 100;
const PROGRESS_DELAY = 250;
const getData = (): Observable<any> => {
  return of('data').pipe(delay(GET_DATA_DELAY));
};
const log = {
  next: (value: any) => console.log(`Date:${ (new Date()).toISOString()} : `, value),
  error: (err: any) => console.error(err),
};
const delay$ = timer(PROGRESS_DELAY).pipe(mapTo('timer'),tap(log));
const data$ = getData().pipe(tap(log));
const isLoading$ = combineLatest([delay$, data$]).pipe(
  mapTo(false),
  startWith(true),
  map(value => `IsLoading: ${value}`),
);
isLoading$.subscribe(log);

// Open the console in the bottom right to see results.