'use strict';
/**
* @author schukai GmbH
*/
import {Monster} from '../namespace.js';
import {TokenList} from './tokenlist.js';
import {isObject} from './is.js';
import {Base} from './base.js';
import {UniqueQueue} from './uniquequeue.js';
/**
* an observer manages a callback function
*
* you can call the method via the monster namespace `new Monster.Types.Observer()`.
*
* ```
* <script type="module">
* import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.5.0/dist/modules/types/observer.js';
* console.log(new Monster.Types.Observer())
* </script>
* ```
*
* Alternatively, you can also integrate this function individually.
*
* ```
* <script type="module">
* import {Observer} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.5.0/dist/modules/types/observer.js';
* console.log(Observer())
* </script>
* ```
*
* 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 the this pointer of its own context.
*
* ```
* <script>
* Observer(()=>{
* // this is not subject
* })
*
* Observer(function() {
* // this is subject
* })
* </script>
* ```
*
* additional arguments can be passed to the callback. to do this, simply specify them.
*
* ```
* <script>
* Observer(function(a, b, c) {
* console.log(a, b, c); // ↦ "a", 2, true
* }, "a", 2, true)
* </script>
* ```
*
* the callback function must have as many parameters as arguments are given.
*
*
* @since 1.0.0
* @copyright schukai GmbH
* @memberOf Monster/Types
*/
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();
}
/**
*
* @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 {Array}
*/
getTags() {
return this.tags.entries()
}
/**
*
* @param {string} tag
* @returns {boolean}
*/
hasTag(tag) {
return this.tags.contains(tag)
}
/**
*
* @param {object} subject
* @returns {Promise}
*/
update(subject) {
let self = this;
return new Promise(function (resolve, reject) {
if (!isObject(subject)) {
reject("subject must be an object");
}
self.queue.add(subject);
setTimeout(() => {
// the queue and the settimeout ensure that an object is not
// informed of the same change more than once.
if (self.queue.isEmpty()) {
resolve();
return;
}
let s = self.queue.poll();
let result = self.callback.apply(s, self.arguments);
if (isObject(result) && result instanceof Promise) {
result.then(resolve).catch(reject);
return;
}
resolve(result);
}, 0)
});
};
}
Monster.assignToNamespace('Monster.Types', Observer);
export {Monster, Observer}