UNPKG

@surface/custom-element

Version:

Provides support of directives and data binding on custom elements.

107 lines (106 loc) 3.59 kB
import { AggregateError, resolveError, runAsync } from "@surface/core"; class Queue { constructor() { this._length = 0; this.node = null; this.lastNode = null; } get length() { return this._length; } enqueue(value) { const node = { value }; if (this.node) { this.lastNode.next = node; } else { this.node = node; } this.lastNode = node; this._length++; } dequeue() { const value = this.node?.value; this.node = this.node?.next ?? null; this._length--; if (this._length == 0) { this.lastNode = null; } return value ?? null; } } export default class Scheduler { constructor(timeout) { this.errors = []; this.highPriorityQueue = new Queue(); this.lowPriorityQueue = new Queue(); this.normalPriorityQueue = new Queue(); this.currentExecution = Promise.resolve(); this.running = false; this.timeout = timeout; } async nextFrame() { return new Promise(resolve => window.requestAnimationFrame(resolve)); } async processQueue(queue, hasPriorityChange) { let expended = 0; while (queue.length > 0) { const [task, resolve, reject, cancellationToken] = queue.dequeue(); const start = window.performance.now(); if (cancellationToken?.canceled) { resolve(undefined); } else { try { resolve(task()); } catch (e) { const error = resolveError(e); this.errors.push(error); reject(error); } } const end = window.performance.now(); expended += end - start; if (expended > this.timeout) { expended = 0; await this.nextFrame(); } if (hasPriorityChange?.()) { await this.execute(); } } } async execute() { await this.processQueue(this.highPriorityQueue); await this.processQueue(this.normalPriorityQueue, () => this.highPriorityQueue.length > 0); await this.processQueue(this.lowPriorityQueue, () => this.highPriorityQueue.length > 0 || this.normalPriorityQueue.length > 0); } start() { this.errors.length = 0; this.running = true; this.currentExecution = runAsync(async () => (await this.execute(), this.stop())); } stop() { this.running = false; if (this.errors.length > 0) { throw new AggregateError([...this.errors]); } } async enqueue(task, priority, cancellationToken) { if (!this.running) { this.start(); } switch (priority) { case "high": return new Promise((resolve, reject) => this.highPriorityQueue.enqueue([task, resolve, reject, cancellationToken])); case "low": return new Promise((resolve, reject) => this.lowPriorityQueue.enqueue([task, resolve, reject, cancellationToken])); default: return new Promise((resolve, reject) => this.normalPriorityQueue.enqueue([task, resolve, reject, cancellationToken])); } } async execution() { return this.currentExecution; } }