UNPKG

@schukai/monster

Version:

Monster is a simple library for creating fast, robust and lightweight websites.

159 lines (141 loc) 4.17 kB
/** * Copyright © Volker Schukai and all contributing authors, {{copyRightYear}}. All rights reserved. * Node module: @schukai/monster * * This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3). * The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html * * For those who do not wish to adhere to the AGPLv3, a commercial license is available. * Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms. * For more information about purchasing a commercial license, please contact Volker Schukai. * * SPDX-License-Identifier: AGPL-3.0 */ import { Base } from "./base.mjs"; import { isObject } from "./is.mjs"; import { TokenList } from "./tokenlist.mjs"; import { instanceSymbol } from "../constants.mjs"; export { Observer }; /** * Manages a callback function that is executed asynchronously when updated. * * The `update` method is called with the subject object as its `this` context. * For this reason, the callback should be a regular function, not an arrow function, * if you need access to the subject via `this`. * * @example * // Basic usage with a regular function to access the subject * const observer = new Observer(function() { * console.log("Subject updated:", this); // `this` refers to `mySubject` * }); * * // Usage with arguments * const greeter = new Observer(function(greeting) { * console.log(greeting, this.name); // "Hello", "World" * }, "Hello"); * * const mySubject = { name: "World" }; * observer.update(mySubject); * greeter.update(mySubject); * * @license AGPLv3 * @since 1.0.0 */ class Observer extends Base { /** * Stores promises for updates that are scheduled but not yet executed. * This prevents multiple executions for the same subject within one microtask cycle. * @type {Map<object, Promise<*>>} */ #pendingUpdates = new Map(); #callback; #arguments; #tags = new TokenList(); /** * @param {function} callback The function to execute on update. * @param {...*} args Additional arguments to pass to the callback. */ constructor(callback, ...args) { super(); if (typeof callback !== "function") { throw new Error("Observer callback must be a function."); } this.#callback = callback; this.#arguments = args; } /** * This method is called by the `instanceof` operator. * @returns {symbol} * @since 2.1.0 */ static get [instanceSymbol]() { return Symbol.for("@schukai/monster/types/observer"); } // Getter for properties for cleaner access if needed get callback() { return this.#callback; } get arguments() { return this.#arguments; } /** * Schedules the callback for execution with the given subject. * If multiple updates for the same subject are requested in the same event loop tick, * the callback is only executed once. All callers will receive the same promise. * * @param {object} subject The subject object that triggered the update. * @returns {Promise<*>} A promise that resolves with the return value of the callback, * or rejects if the callback throws an error. */ update(subject) { if (!isObject(subject)) { return Promise.reject(new Error("Subject must be an object.")); } if (this.#pendingUpdates.has(subject)) { return this.#pendingUpdates.get(subject); } const promise = new Promise((resolve, reject) => { queueMicrotask(async () => { try { const result = await this.#callback.apply(subject, this.#arguments); resolve(result); } catch (e) { reject(e); } finally { this.#pendingUpdates.delete(subject); } }); }); this.#pendingUpdates.set(subject, promise); return promise; } /** * @param {string} tag * @returns {Observer} */ addTag(tag) { this.#tags.add(tag); return this; } /** * @param {string} tag * @returns {Observer} */ removeTag(tag) { this.#tags.remove(tag); return this; } /** * @returns {string[]} */ getTags() { return this.#tags.entries(); } /** * @param {string} tag * @returns {boolean} */ hasTag(tag) { return this.#tags.contains(tag); } }