File size: 1,493 Bytes
dbb1bf9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
interface QueueEntry {
  reject: (reason?: unknown) => void;
  resolve: (value: unknown) => void;
  task: () => Promise<unknown> | unknown;
}

/**
 * Small FIFO executor for browser-side async work that must not overlap.
 *
 * The first task starts synchronously so unload/visibility handlers can begin
 * a keepalive fetch before returning. Later tasks wait without allowing a
 * rejection to poison the queue.
 */
export class SerializedAsyncQueue {
  private active = false;
  private readonly entries: QueueEntry[] = [];

  get busy(): boolean {
    return this.active || this.entries.length > 0;
  }

  run<T>(task: () => Promise<T> | T): Promise<T> {
    return new Promise<T>((resolve, reject) => {
      this.entries.push({
        task,
        resolve: resolve as (value: unknown) => void,
        reject,
      });
      this.startNext();
    });
  }

  private startNext(): void {
    if (this.active) return;

    const entry = this.entries.shift();
    if (!entry) return;

    this.active = true;
    let result: Promise<unknown> | unknown;
    try {
      result = entry.task();
    } catch (error) {
      this.active = false;
      this.startNext();
      entry.reject(error);
      return;
    }

    void Promise.resolve(result).then(
      (value) => {
        this.active = false;
        this.startNext();
        entry.resolve(value);
      },
      (error) => {
        this.active = false;
        this.startNext();
        entry.reject(error);
      },
    );
  }
}