I have a Node.js application (using sequelize as orm) which i try to separate into different modules which can easily be reused. This means I need to remove some dependencies between them, but I'm struggling at one point:
There's a module called "Account", which should have (almost) zero dependencies on other modules. It just provides basic account functionality.
Then there are other modules, which have a dependency on the account module (which is ok).
At the moment, the Account module has a dependency on the other modules, which I'd like to resolve. Now, when an account gets created, other modules also must create some objects in the database. This should happen in the same transaction and without the account module knowing something about the other modules.
Currently, this looks like the following:
AccountController.prototype.createAccount = function (data) {
// validation checks etc. omited
return db.sequelize.transaction(function (t) {
return Q.Promise(function (resolve, reject, notify) {
_createHash(pw, 10)
.then(function (hash) {
data.passwordHash = hash;
return _createAccount(data, t);
})
.then(function (account) {
// create user
return [account, _createUser(account, t)];
})
.spread(function (account, user) {
// create another object
return [account, user, _createXXX(account, t)]
})
.spread(function(account, user, xxx) {
// create 1 more object
return [account, user, xxx, _createYYY(account, t)];
})
.spread(function (account, user, xxx, yyy) {
resolve([account, user, xxx, yyy]);
})
.catch(reject);
});
});
};
Now, I only want to create the Account object within this module, and let the other modules create their objects independent, but in the same transaction.
First I thought about let the AccountController emit an "createAccount" Event within the promise chain, hand over the transaction object and let the modules register some listeners. But then I noticed that EventEmitter works asynchronously.
What's the best practice in Node.js to do something like this?
Ok let me answer the question myself:
Here's the code I came up with:
'use strict';
var EventEmitter = require('events');
var util = require('util');
var Q = require('q');
function PromisedEventEmitter() {
}
util.inherits(PromisedEventEmitter, EventEmitter);
PromisedEventEmitter.prototype.promisedEmit = function promisedEmit(type) {
var er, handler, len, args, i, listeners;
var promises = [];
if (!this._events)
this._events = {};
// If there is no 'error' event listener then throw.
if (type === 'error' && !this._events.error) {
er = arguments[1];
if (this.domain) {
if (!er)
er = new Error('Uncaught, unspecified "error" event.');
er.domainEmitter = this;
er.domain = this.domain;
er.domainThrown = false;
this.domain.emit('error', er);
} else if (er instanceof Error) {
throw er; // Unhandled 'error' event
} else {
throw Error('Uncaught, unspecified "error" event.');
}
return Q.all(promises);
}
handler = this._events[type];
if (util.isUndefined(handler))
return Q.all(promises);
if (this.domain && this !== process)
this.domain.enter();
var promise;
if (util.isFunction(handler)) {
switch (arguments.length) {
// fast cases
case 1:
promise = handler.call(this);
break;
case 2:
promise = handler.call(this, arguments[1]);
break;
case 3:
promise = handler.call(this, arguments[1], arguments[2]);
break;
// slower
default:
len = arguments.length;
args = new Array(len - 1);
for (i = 1; i < len; i++)
args[i - 1] = arguments[i];
promise = handler.apply(this, args);
}
promises.push(promise);
} else if (util.isObject(handler)) {
len = arguments.length;
args = new Array(len - 1);
for (i = 1; i < len; i++)
args[i - 1] = arguments[i];
listeners = handler.slice();
len = listeners.length;
for (i = 0; i < len; i++) {
promise = listeners[i].apply(this, args);
promises.push(promise);
}
}
if (this.domain && this !== process)
this.domain.exit();
return Q.all(promises);
};
module.exports = PromisedEventEmitter;
This is basically just the original .emit code, I only added some promise handling to it. I now can use this the same way as the original EventEmitter, and just call
XXXController
.promisedEmit("eventName", arg1, arg2, argN)]
.then(function(){
//...
});
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.