UNPKG

6.34 kBJavaScriptView Raw
1/**
2 * @license
3 * MOST Web Framework 2.0 Codename Blueshift
4 * Copyright (c) 2017, THEMOST LP All rights reserved
5 *
6 * Use of this source code is governed by an BSD-3-Clause license that can be
7 * found in the LICENSE file at https://themost.io/license
8 */
9///
10var EventEmitter = require('events').EventEmitter;
11var LangUtils = require('./utils').LangUtils;
12var applyEachSeries = require('async').applyEachSeries;
13require('es6-promise/auto');
14
15/**
16 * Wraps an async listener and returns a callback-like function
17 * @param {function(...*):Promise<void>} asyncListener
18 */
19function wrapAsyncListener(asyncListener) {
20 /**
21 * @this SequentialEventEmitter
22 */
23 var result = function() {
24 // get arguments without callback
25 var args = [].concat(Array.prototype.slice.call(arguments, 0, arguments.length -1));
26 // get callback
27 var callback = arguments[arguments.length - 1];
28 return asyncListener.apply(this, args).then(function() {
29 return callback();
30 }).catch(function(err) {
31 return callback(err);
32 });
33 }
34 // set async listener property in order to have an option to unsubscribe
35 Object.defineProperty(result, '_listener', {
36 configurable: true,
37 enumerable: true,
38 value: asyncListener
39 });
40 return result;
41}
42
43/**
44 * Wraps an async listener and returns a callback-like function
45 * @param {string} event
46 * @param {function(...*):Promise<void>} asyncListener
47 */
48function wrapOnceAsyncListener(event, asyncListener) {
49 /**
50 * @this SequentialEventEmitter
51 */
52 var result = function() {
53 var callee = arguments.callee;
54 // get arguments without callback
55 var args = [].concat(Array.prototype.slice.call(arguments, 0, arguments.length -1));
56 // get callback
57 var callback = arguments[arguments.length - 1];
58 var self = this;
59 return asyncListener.apply(self, args).then(function() {
60 // manually remove async listener
61 self.removeListener(event, callee);
62 return callback();
63 }).catch(function(err) {
64 // manually remove async listener
65 self.removeListener(event, callee);
66 return callback(err);
67 });
68 }
69 // set async listener property in order to have an option to unsubscribe
70 Object.defineProperty(result, '_listener', {
71 configurable: true,
72 enumerable: true,
73 value: asyncListener
74 });
75 return result;
76}
77
78// noinspection JSClosureCompilerSyntax,JSClosureCompilerSyntax,JSClosureCompilerSyntax,JSClosureCompilerSyntax
79/**
80 * SequentialEventEmitter class is an extension of node.js EventEmitter class where listeners are executing in series.
81 * @class
82 * @constructor
83 * @augments EventEmitter
84 */
85function SequentialEventEmitter() {
86 //
87}
88LangUtils.inherits(SequentialEventEmitter, EventEmitter);
89
90// noinspection JSUnusedGlobalSymbols
91/**
92 * Executes event listeners in series.
93 * @param {String} event - The event that is going to be executed.
94 * @param {...*} args - An object that contains the event arguments.
95 */
96// eslint-disable-next-line no-unused-vars
97SequentialEventEmitter.prototype.emit = function(event, args)
98{
99 //ensure callback
100 callback = callback || function() {};
101 //get listeners
102 if (typeof this.listeners !== 'function') {
103 throw new Error('undefined listeners');
104 }
105 var listeners = this.listeners(event);
106
107 var argsAndCallback = [].concat(Array.prototype.slice.call(arguments, 1));
108 if (argsAndCallback.length > 0) {
109 //check the last argument (expected callback function)
110 if (typeof argsAndCallback[argsAndCallback.length - 1] !== "function") {
111 throw new TypeError("Expected event callback");
112 }
113 }
114 //get callback function (the last argument of arguments list)
115 var callback = argsAndCallback[argsAndCallback.length - 1];
116
117 //validate listeners
118 if (listeners.length===0) {
119 //exit emitter
120 return callback();
121 }
122 //apply each series
123 return applyEachSeries.apply(this, [listeners].concat(argsAndCallback));
124};
125// noinspection JSUnusedGlobalSymbols
126/**
127 *
128 * @param {string} event
129 * @param {function(...*):Promise<void>} asyncListener
130 * @returns this
131 */
132SequentialEventEmitter.prototype.subscribe = function(event, asyncListener) {
133 return this.on(event, wrapAsyncListener(asyncListener));
134}
135
136// noinspection JSUnusedGlobalSymbols
137/**
138 *
139 * @param {string} event
140 * @param {function(...*):Promise<void>} asyncListener
141 * @returns this
142 */
143SequentialEventEmitter.prototype.unsubscribe = function(event, asyncListener) {
144 // get event listeners
145 var listeners = this.listeners(event);
146 // enumerate
147 for (var i = 0; i < listeners.length; i++) {
148 var item = listeners[i];
149 // if listener has an underlying listener
150 if (typeof item._listener === 'function') {
151 // and it's the same with the listener specified
152 if (item._listener === asyncListener) {
153 // remove listener and break
154 this.removeListener(event, item);
155 break;
156 }
157 }
158 }
159 return this;
160}
161
162// noinspection JSUnusedGlobalSymbols
163/**
164 *
165 * @param {string} event
166 * @param {function(...*):Promise<void>} asyncListener
167 */
168SequentialEventEmitter.prototype.subscribeOnce = function(event, asyncListener) {
169 return this.once(event, wrapOnceAsyncListener(event, asyncListener));
170}
171
172// noinspection JSUnusedGlobalSymbols
173/**
174 *
175 * @param {string} event
176 * @param {...args} args
177 */
178// eslint-disable-next-line no-unused-vars
179SequentialEventEmitter.prototype.next = function(event, args) {
180 var self = this;
181 /**
182 * get arguments as array
183 * @type {*[]}
184 */
185 var argsAndCallback = [event].concat(Array.prototype.slice.call(arguments, 1));
186 // eslint-disable-next-line no-undef
187 return new Promise(function(resolve, reject) {
188 // set callback
189 argsAndCallback.push(function(err) {
190 if (err) {
191 return reject(err);
192 }
193 return resolve();
194 });
195 // emit event
196 self.emit.apply(self, argsAndCallback);
197 });
198
199}
200
201if (typeof exports !== 'undefined') {
202 module.exports.SequentialEventEmitter = SequentialEventEmitter;
203}