happy-dom
Version:
Happy DOM is a JavaScript implementation of a web browser without its graphical user interface. It includes many web standards from WHATWG DOM and HTML.
196 lines • 5.81 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
// We need to set this as a global constant, so that using fake timers in Jest and Vitest won't override this on the global object.
const TIMER = {
setImmediate: globalThis.setImmediate.bind(globalThis),
clearImmediate: globalThis.clearImmediate.bind(globalThis),
clearTimeout: globalThis.clearTimeout.bind(globalThis)
};
/**
* Handles async tasks.
*/
class AsyncTaskManager {
constructor() {
this.runningTasks = {};
this.runningTaskCount = 0;
this.runningTimers = [];
this.runningImmediates = [];
this.waitUntilCompleteTimer = null;
this.waitUntilCompleteResolvers = [];
}
/**
* Returns a promise that is resolved when async tasks are complete.
*
* @returns Promise.
*/
waitUntilComplete() {
return new Promise((resolve) => {
this.waitUntilCompleteResolvers.push(resolve);
this.endTask(this.startTask());
});
}
/**
* Aborts all tasks.
*/
abort() {
return this.abortAll(false);
}
/**
* Destroys the manager.
*/
destroy() {
return this.abortAll(true);
}
/**
* Starts a timer.
*
* @param timerID Timer ID.
*/
startTimer(timerID) {
this.runningTimers.push(timerID);
}
/**
* Ends a timer.
*
* @param timerID Timer ID.
*/
endTimer(timerID) {
const index = this.runningTimers.indexOf(timerID);
if (index !== -1) {
this.runningTimers.splice(index, 1);
if (!this.runningTaskCount && !this.runningTimers.length && !this.runningImmediates.length) {
this.resolveWhenComplete();
}
}
}
/**
* Starts an immediate.
*
* @param immediateID Immediate ID.
*/
startImmediate(immediateID) {
this.runningImmediates.push(immediateID);
}
/**
* Ends an immediate.
*
* @param immediateID Immediate ID.
*/
endImmediate(immediateID) {
const index = this.runningImmediates.indexOf(immediateID);
if (index !== -1) {
this.runningImmediates.splice(index, 1);
if (!this.runningTaskCount && !this.runningTimers.length && !this.runningImmediates.length) {
this.resolveWhenComplete();
}
}
}
/**
* Starts an async task.
*
* @param abortHandler Abort handler.
* @returns Task ID.
*/
startTask(abortHandler) {
const taskID = this.newTaskID();
this.runningTasks[taskID] = abortHandler ? abortHandler : () => { };
this.runningTaskCount++;
return taskID;
}
/**
* Ends an async task.
*
* @param taskID Task ID.
*/
endTask(taskID) {
if (this.runningTasks[taskID]) {
delete this.runningTasks[taskID];
this.runningTaskCount--;
if (this.waitUntilCompleteTimer) {
TIMER.clearImmediate(this.waitUntilCompleteTimer);
}
if (!this.runningTaskCount && !this.runningTimers.length && !this.runningImmediates.length) {
this.waitUntilCompleteTimer = TIMER.setImmediate(() => {
this.waitUntilCompleteTimer = null;
if (!this.runningTaskCount &&
!this.runningTimers.length &&
!this.runningImmediates.length) {
this.resolveWhenComplete();
}
});
}
}
}
/**
* Returns the amount of running tasks.
*
* @returns Count.
*/
getTaskCount() {
return this.runningTaskCount;
}
/**
* Returns a new task ID.
*
* @returns Task ID.
*/
newTaskID() {
this.constructor.taskID++;
return this.constructor.taskID;
}
/**
* Resolves when complete.
*/
resolveWhenComplete() {
const resolvers = this.waitUntilCompleteResolvers;
this.waitUntilCompleteResolvers = [];
for (const resolver of resolvers) {
resolver();
}
}
/**
* Aborts all tasks.
*
* @param destroy Destroy.
*/
abortAll(destroy) {
const runningTimers = this.runningTimers;
const runningImmediates = this.runningImmediates;
const runningTasks = this.runningTasks;
this.runningTasks = {};
this.runningTaskCount = 0;
this.runningImmediates = [];
this.runningTimers = [];
if (this.waitUntilCompleteTimer) {
TIMER.clearImmediate(this.waitUntilCompleteTimer);
this.waitUntilCompleteTimer = null;
}
for (const immediate of runningImmediates) {
TIMER.clearImmediate(immediate);
}
for (const timer of runningTimers) {
TIMER.clearTimeout(timer);
}
const taskPromises = [];
for (const key of Object.keys(runningTasks)) {
const returnValue = runningTasks[key](destroy);
if (returnValue instanceof Promise) {
taskPromises.push(returnValue);
}
}
if (taskPromises.length) {
return Promise.all(taskPromises)
.then(() => this.waitUntilComplete())
.catch((error) => {
/* eslint-disable-next-line no-console */
console.error(error);
throw error;
});
}
// We need to wait for microtasks to complete before resolving.
return this.waitUntilComplete();
}
}
AsyncTaskManager.taskID = 0;
exports.default = AsyncTaskManager;
//# sourceMappingURL=AsyncTaskManager.cjs.map