5

我有一个执行昂贵的同步任务的函数。在我的例子中,它是通过 pdfkit 生成的客户端 pdf,但让我们用 while-loop sleep 来模拟它。

我想在运行任务之前显示一个“加载”微调器,并在完成后隐藏它。

如果一切都是同步运行的,Angular 将没有机会在一切结束之前运行更改检测循环,所以我想我只需要找到一种异步运行它的方法。

我尝试将它包装在一个承诺中,所以假设我有:

sleep(ms) {
  return new Promise((resolve, reject) => {
    const expire = (new Date()).getTime() + ms;
    while ((new Date()).getTime() < expire);
    resolve();
  });
}

但是我是否使用 .then() 调用来运行它:

run() {
  this.running = true;
  this.sleep(2000).then(() => {
    this.running = false;
  });
}

或使用异步/等待:

async run() {
  this.running = true;
  await this.sleep(2000);
  this.running = false;
}

在函数结束之前不会检测到更改,并且不会显示任何内容。

我猜问题是Javascript仍然是单线程的,并且promise在创建时仍然会立即运行,所以一切基本上仍然是同步运行的。

但即使使用 ChangeDetectorRef.detectChanges() 强制进行更改检测也无济于事。

到目前为止,我发现的唯一解决方案是在 setTimeout hack 中运行它:

setTimeoutRun() {
  this.running = true;
  setTimeout(() => {
    this.sleep(2000);
    this.running = false;
  }, 100);
}

但它看起来不像是正确的正式解决方案。

setTimeout 真的是唯一的方法吗?

Plunker:https ://embed.plnkr.co/ywsIhulPMqjqwzaMxVjn/

4

2 回答 2

1

如果您的工作是同步的,那么您的加载逻辑也需要同步。除了利用setTimeout.

换句话说,你不能做类似的事情this.loading = true,因为这将不得不等待变更检测运行。您必须显式启动加载逻辑(手动将加载器元素添加到 DOM 以便立即可见等)。

否则,根据定义,它必须等到您的长同步作业完成才能开始加载,因为加载器逻辑本身是异步的,因此只有在当前执行(即您的同步作业)完成后才会被调用。

例如:

@Component({...})
export class MyComponent implements OnInit {
    constructor(private loadingService: LoadingService) {}

    ngOnInit() {
        // Start synchronous loader...
        this.loadingService.start();

        // By the time code reaches here, loader should be visible.

        // Do expensive synchronous task...
        this.expensiveSynchronousTask().then(() => {

            // Stop synchronous loader...
            this.loadingService.stop();

        });
    }
}
于 2018-03-29T18:05:54.783 回答
0

好的,给你。这个带有 Observable.timer() 的解决方案应该可以解决您的问题。计时器等待 2 秒,然后将 running then 设置为 false。

import { Component, NgModule, OnInit, OnDestroy } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { Observable, Subscription } from 'rxjs/Rx';


@Component({
    selector: 'my-app',
    template: `
    <button (click)="run()">Run</button>
    <div *ngIf="running">Running...</div>
  `
})
export class App implements OnInit, OnDestroy {
    running = false;

    private timer = Observable.timer(2000);
    private subscription: Subscription;

    constructor() { }

    ngOnInit() { }

    ngOnDestroy() {
        if (this.subscription) {
            this.subscription.unsubscribe();
        }
    }

    run() {
        this.running = true;
        this.subscription = this.timer.subscribe(() => {
            this.running = false;
            this.subscription.unsubscribe();
        });
    }
}
于 2018-03-30T10:24:45.420 回答