lib/promise.js

1.
const assert = require("assert");
2.

			
3.
const error_events = ["err", "error", "catch", "failure", "fail"];
4.
const then_handlers = ["then", "then2"];
5.

			
6.
/**
7.
 * This class is used to create a fuly custom promise. Why a new type of promises? Since it 
8.
 * supports the usage of events, stacking, children and some other features. It is mostly the same 
9.
 * as the original promises with a few differences. The first difference is that the resolver 
10.
 * function receives just one argument: the event argument. When you call this argument the first 
11.
 * parameter you supply is the name of the event and after that you can supply the remaining 
12.
 * parameters you want to pass through. You can also add handlers to the promise using the .on(), 
13.
 * .then() and .catch(), each of which is explained in the function descriptions. When an event is 
14.
 * created, it is added to the queue and when there is an update each item in the queue will be 
15.
 * checked and the matched handlers per item will be called. When a handler returns a promise, 
16.
 * which shall be named "child", all handlers added after the child returning handler will be added
17.
 * to the child. It is also important to note that the first tick the promise is locked, meaning no
18.
 * events will be called. This is because this allows for multiple handlers to be added without the
19.
 * first added handler clearing out all the events.
20.
 * @example
21.
 * cerus.promise(function(event) {
22.
 *   event("done", "test");
23.
 * })
24.
 * .on("done", console.log);
25.
 * // logs "test"
26.
 * @example
27.
 * cerus.promise(function(event) {
28.
 *   event("done", "test");
29.
 * })
30.
 * .on("done", function(value) {
31.
 *   return cerus.promise(function(event) {
32.
 *     event("other_event", value);
33.
 *   });
34.
 * })
35.
 * .on("other_event", console.log);
36.
 * // logs "test"
37.
 * @param {Function|Promise} (executor) The function or promise to listen for events on.
38.
 * @class promise
39.
 */
40.
class promise {
41.
	constructor(executor) {
42.
		this._handlers = [];
43.
		this._locked = true;
44.
		this._queue = [];
45.

			
46.
		setTimeout(() => {
47.
			this._locked = false;
48.
			this._update();
49.
		}, 0);
50.

			
51.
		if(executor instanceof Promise) {
52.
			executor.then((...args) => this.event("success", ...args));
53.
			executor.catch(err => this.event("error", err));
54.
		}
55.
		else if(typeof executor === "function") {
56.
			try {
57.
				executor.call(this, this.event.bind(this));
58.
			} 
59.
			catch (e) {
60.
				this.event("error", e);
61.
			}
62.
		}
63.

			
64.
		return this;
65.
	}
66.

			
67.
	/**
68.
	 * This function updates all the events that are in the queue. It does this by looping through 
69.
	 * all the events in the queue and matching all the handlers there are listening for every 
70.
	 * event. Those handlers then get called. If the promise is locked it will return and do 
71.
	 * nothing. After looping through all the events in queue the queue is cleared.
72.
	 * @summary Updates all the events in the queue.
73.
	 * @access internal
74.
	 * @function _update
75.
	 */
76.
	_update() {
77.
		if(this._locked) {
78.
			return;
79.
		}
80.

			
81.
		this._queue.forEach(item => {
82.
			const matched_handlers = this._handlers.filter(handler => this._match_handler(handler, item.event));
83.

			
84.
			if(error_events.includes(item.event) && matched_handlers.length === 0) {
85.
				console.error("UnhandledPromiseRejectionWarning: " + item.args[0]);
86.
			}
87.

			
88.
			matched_handlers.forEach(handler => {
89.
				if(!this._handlers.includes(handler)) {
90.
					return;
91.
				}
92.

			
93.
				let data;
94.

			
95.
				if(handler.event === "then") {
96.
					data = handler.callback(item.event, ...item.args);
97.
				}
98.
				else {
99.
					data = handler.callback(...item.args);
100.
				}
101.

			
102.
				if(data instanceof promise && data !== this) {
103.
					this._parentify(data, this._handlers.indexOf(handler) + 1);
104.
				}
105.
				else if(data !== undefined && handler.options.passthrough) {
106.
					let args = [];
107.

			
108.
					if(handler.options.multi && data instanceof Array) {
109.
						args.push(...data);
110.
					}
111.
					else {
112.
						args.push(data);
113.
					}
114.

			
115.
					this._parentify(new promise().event(item.event, ...args), this._handlers.indexOf(handler) + 1);
116.
				}
117.
			});
118.
		});
119.

			
120.
		this._queue = [];
121.
	}
122.

			
123.
	_match_handler(handler, event) {
124.
		return handler.event === event || 
125.
			(handler.event === "error" && error_events.includes(event)) ||
126.
			(then_handlers.includes(handler.event) && !error_events.includes(event));
127.
	}
128.

			
129.
	_parentify(child, handlers_index) {
130.
		assert(handlers_index >= 0, "The index of the function that returns the child wasn't found");
131.

			
132.
		if(this._child === undefined) {
133.
			this._parentified_handlers = this._handlers.slice(handlers_index);			
134.
		}
135.
		else {
136.
			// Remove the handlers that were added when the original child was set as child
137.
			this._child._handlers.splice(this._child_handlers_index, this._parentified_handlers.length);
138.

			
139.
			// Add any newly added handlers
140.
			this._parentified_handlers = this._parentified_handlers.concat(this._handlers.slice(handlers_index));
141.
		}
142.

			
143.
		this._child_handlers_index = child._handlers.length - 1;
144.

			
145.
		child._handlers = child._handlers.concat(this._parentified_handlers);
146.
		child._update();
147.

			
148.
		this._handlers = this._handlers.slice(0, handlers_index);
149.
		this._child = child;
150.
	}
151.

			
152.
	/**
153.
	 * This function will call the specified event. This function is passed into the promise 
154.
	 * function, but can also be used to call events from outside of the promise. You can also add
155.
	 * arguments by just adding them to function. What events cause what to happen, like "then" and
156.
	 * "catch" functions is explained in the class description.
157.
	 * @summary Calls the specified event.
158.
	 * @param {String} event The name of the event to call.
159.
	 * @param  {...Any} args The arguments for the event that will be called.
160.
	 * @returns {Self} This function returns itself for chaining.
161.
	 * @function event
162.
	 */
163.
	event(event, ...args) {
164.
		if(event === undefined) {
165.
			return this;
166.
		}
167.

			
168.
		if(this._child && error_events.includes(event)) {
169.
			this._child.event(event, ...args);
170.
		}
171.

			
172.
		if(this._stacked !== undefined) {
173.
			this._stacked(event, args);
174.

			
175.
			return this;
176.
		}
177.

			
178.
		this._queue[this._queue.length] = {
179.
			args, event
180.
		};
181.

			
182.
		this._update();
183.

			
184.
		return this;
185.
	}
186.

			
187.
	/**
188.
	 * With this function you can create a handler that responds to all non-error events. The 
189.
	 * meaning of a handler is explained in the class description. You can also supply the 
190.
	 * event option which sets if the first argument when the handler is called is the 
191.
	 * name of the event. By default this is set to false.
192.
	 * @summary Creates a handler that responds to non-error events.
193.
	 * @param {Function} callback The callback that will be called on non-error events.
194.
	 * @param {Object} (options) The optional options object.
195.
	 * @param {Boolean} (options.event = false) If the first argument when the handler is called is the event.
196.
	 * @param {Boolean} (options.passthrough = faslse) If the value returned by the handler may be treaded as a resolved promise.
197.
	 * @returns {Self} This function returns itself for chaining.
198.
	 * @function then
199.
	 */
200.
	then(callback, options = {}) {
201.
		const event = options.event ? "then" : "then2";
202.

			
203.
		return this._handler(event, callback, options);
204.
	}
205.

			
206.
	/**
207.
	 * With this function you can create a handler that responds to all error events. The meaning
208.
	 * of a handler is explained in the class description. The error events are: "err", "error", 
209.
	 * "catch" and "failure".
210.
	 * @summary Creates a handler that responds to error events.
211.
	 * @param {Function} callback The callback that will be called on error events.
212.
	 * @param {Object} (options) The optional options object.
213.
	 * @param {Boolean} (options.passthrough = faslse) If the value returned by the handler may be treaded as a resolved promise.
214.
	 * @returns {Self} This function returns itself for chaining.
215.
	 * @function catch
216.
	 */
217.
	catch(callback, options = {}) {
218.
		return this._handler("error", callback, options);
219.
	}
220.

			
221.
	/**
222.
	 * With this function you can create a handler that responds to the specified event. The 
223.
	 * meaning of a handler is explained in the class description. You can set the event using the
224.
	 * event parameter.
225.
	 * @summary Creates a handler that response to the specified event.
226.
	 * @param {String} event The event for this handler to listen for.
227.
	 * @param {Function} callback The callback that will be called on the specified event.
228.
	 * @param {Object} (options) The optional options object.
229.
	 * @param {Boolean} (options.passthrough = faslse) If the value returned by the handler may be treaded as a resolved promise.
230.
	 * @returns {Self} This function returns itself for chaining.
231.
	 * @function on
232.
	 */
233.
	on(event, callback, options = {}) {
234.
		return this._handler(event, callback, options);
235.
	}
236.

			
237.
	_handler(event, callback, options, default_options = {}) {
238.
		if(this._child) {
239.
			this._child._handler(...arguments);
240.

			
241.
			return this;
242.
		}
243.

			
244.
		if(callback === this.event) {
245.
			return this.stack(callback);
246.
		}
247.

			
248.
		let _options = Object.assign({}, {
249.
			passthrough: false,
250.
			multi: false
251.
		}, default_options, options);
252.

			
253.
		this._handlers.push({event, callback, options: _options});
254.
		this._update();
255.
	
256.
		return this;
257.
	}
258.

			
259.
	/**
260.
	 * This function is used to stack the promise. Stacking means that you can stack this on top of
261.
	 * another promise. By doing this the other promise will receive the same all the events called
262.
	 * on this promise, basically passing through the event downwards. Stack a promise on top of 
263.
	 * another promise using the event function of the promise being stacked on top of.
264.
	 * @example
265.
	 * cerus.promise(function(event1) {
266.
	 *   cerus.promise(function(event2) {
267.
	 *     event2("done", "test");
268.
	 *   })
269.
	 *   .stack(event1);
270.
	 * })
271.
	 * .on("done", console.log);
272.
	 * // logs "test"
273.
	 * @summary Stack a promise on top of another one.
274.
	 * @param {Function} event The event function of the promise to stack on top of.
275.
	 * @returns {Self} This function returns itself for chaining.
276.
	 * @function stack
277.
	 */
278.
	stack(event) {
279.
		assert.strictEqual(typeof event, "function", "The argument event must be a function");
280.

			
281.
		if(this._child) {
282.
			this._child.stack(...arguments);
283.

			
284.
			return this;
285.
		}
286.

			
287.
		this._locked = false;
288.

			
289.
		const promise = event();
290.

			
291.
		this._stacked = promise.event;
292.
		promise._queue = promise._queue.concat(this._queue);
293.
		promise._update();
294.
	
295.
		return this;
296.
	}
297.

			
298.
	/**
299.
	 * This function returns a real promise that will be resolved when the specified handler has 
300.
	 * resolved. By default the handler that has last been added is promisified. The promise that 
301.
	 * is returned will allways resolve succesfully even if the handler is catch handler.
302.
	 * @example
303.
	 * let test_promise = cerus.promise(event => event("done", "test"));
304.
	 * console.log(await test_promise.shadow());
305.
	 * // logs "test"
306.
	 * @summary Promisifies the specified handler.
307.
	 * @param {Number} (handler_index) The index of the handler to shadow. By default this is the last added handler.
308.
	 * @returns {Promise} The newly created real promise.
309.
	 * @function shadow
310.
	 */
311.
	shadow(handler_index = this._handlers.length - 1) {
312.
		if(this._child) {
313.
			return this._child.shadow(handler_index);
314.
		}
315.

			
316.
		if(this._handlers.length === 0) {
317.
			this.then();
318.

			
319.
			handler_index++;
320.
		}
321.

			
322.
		const handler = this._handlers[handler_index];
323.

			
324.
		assert.notStrictEqual(handler, undefined, "There is no handler with the index " + handler_index);
325.

			
326.
		const original_callback = handler.callback;
327.

			
328.
		return new Promise(success => {
329.
			handler.callback = (...args) => {
330.
				if(typeof original_callback === "function") {
331.
					original_callback(...args);
332.
				}
333.

			
334.
				success(...args);
335.
			};
336.
		});
337.
	}
338.
}
339.

			
340.
module.exports = promise;