UNPKG

@schukai/monster

Version:

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

164 lines (147 loc) 3.65 kB
/** * Copyright © schukai GmbH 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 schukai GmbH. * * SPDX-License-Identifier: AGPL-3.0 */ import { Base } from "./base.mjs"; import { isObject } from "./is.mjs"; import { TokenList } from "./tokenlist.mjs"; import { UniqueQueue } from "./uniquequeue.mjs"; import { instanceSymbol } from "../constants.mjs"; export { Observer }; /** * An observer manages a callback function * * The update method is called with the subject object as this pointer. For this reason * the callback should not be an arrow function, because it gets this pointer of its own context. * * Include this class in your project with the following code: * * ```js * import { Observer } from "@schukai/monster/source/types/observer.mjs"; * ``` * * The callback function is passed as the first argument to the constructor. * * ```js * new Observer(()=>{ * // this is not subject * }) * * new Observer(function() { * // this is subject * }) * ``` * * Additional arguments can be passed to the callback. To do this, simply specify them. * * ```js * Observer(function(a, b, c) { * console.log(a, b, c); // ↦ "a", 2, true * }, "a", 2, true) * ``` * * The callback function must have as many parameters as arguments are given. * * @license AGPLv3 * @since 1.0.0 */ class Observer extends Base { /** * * @param {function} callback * @param {*} args */ constructor(callback, ...args) { super(); if (typeof callback !== "function") { throw new Error("observer callback must be a function"); } this.callback = callback; this.arguments = args; this.tags = new TokenList(); this.queue = new UniqueQueue(); } /** * This method is called by the `instanceof` operator. * @return {symbol} * @since 2.1.0 */ static get [instanceSymbol]() { return Symbol.for("@schukai/monster/types/observer"); } /** * * @param {string} tag * @return {Observer} */ addTag(tag) { this.tags.add(tag); return this; } /** * * @param {string} tag * @return {Observer} */ removeTag(tag) { this.tags.remove(tag); return this; } /** * * @return {Array} */ getTags() { return this.tags.entries(); } /** * * @param {string} tag * @return {boolean} */ hasTag(tag) { return this.tags.contains(tag); } /** * * @param {object} subject * @return {Promise} */ update(subject) { const self = this; if (!isObject(subject)) { return Promise.reject("subject must be an object"); } return new Promise(function (resolve, reject) { self.queue.add(subject); queueMicrotask(() => { try { // the queue and the `queueMicrotask` ensure that an object is not // informed of the same change more than once. if (self.queue.isEmpty()) { resolve(); return; } const s = self.queue.poll(); const result = self.callback.apply(s, self.arguments); if (isObject(result) && result instanceof Promise) { result.then(resolve).catch(reject); return; } resolve(result); } catch (e) { reject(e); } }); }); } }