express-promise
Version:
An [express.js](http://expressjs.com) middleware for easy rendering async query.
210 lines (193 loc) • 5.55 kB
JavaScript
var isPromise = function(v) {
return v !== null && typeof v === 'object' && typeof v.then === 'function';
};
var isMongooseQuery = function(v) {
return v !== null && typeof v === 'object' && typeof v.exec === 'function';
};
var resolveAsync = function(object, callback, count, options) {
if (!object || typeof object !== 'object') {
return callback(null, object);
}
if (count === 0) {
return callback(new Error('Max promises (' + options.maxPromise + ') reached'));
}
if (isPromise(object)) {
return object.then(function(result) {
if (isPromise(result) || isMongooseQuery(result)) {
resolveAsync(result, callback, count - 1, options);
} else {
callback(null, result);
}
}, function(err) {
callback(err);
});
}
if (isMongooseQuery(object)) {
return object.exec(function(err, result) {
if (err) {
callback(err);
}
if (isPromise(result) || isMongooseQuery(result)) {
resolveAsync(result, callback, count - 1, options);
} else {
callback(err, result);
}
});
}
if (options.skipTraverse && options.skipTraverse(object)) {
return callback(null, object);
}
if (typeof object.toJSON === 'function') {
object = object.toJSON();
if (!object || typeof object !== 'object') {
return callback(null, object);
}
}
var remains = [];
Object.keys(object).forEach(function(key) {
if (isPromise(object[key]) || isMongooseQuery(object[key])) {
object[key].key = key;
remains.push(object[key]);
} else if (typeof object[key] === 'object') {
remains.push({
key: key,
});
}
});
if (!remains.length) {
return callback(null, object);
}
var pending = remains.length;
remains.forEach(function(item) {
function handleDone(err, result) {
if (err) {
return callback(err);
}
object[item.key] = result;
if (--pending === 0) {
callback(null, object);
}
}
if (isPromise(item)) {
item.then(function(result) {
handleDone(null, result);
}, function(err) {
handleDone(err);
});
} else if (isMongooseQuery(item)) {
item.exec(function(err, result) {
handleDone(err, result);
});
} else {
resolveAsync(object[item.key], handleDone, count - 1, options);
}
});
};
var expressPromise = function(options) {
var defaultOptions = {
methods: ['json', 'render', 'send'],
maxPromise: 20
};
options = options || {};
Object.keys(defaultOptions).forEach(function(key) {
if (typeof options[key] === 'undefined') {
options[key] = defaultOptions[key];
}
});
return function(_, res, next) {
if (typeof next !== 'function') {
next = function() {};
}
if (~options.methods.indexOf('json')) {
var originalResJson = res.json.bind(res);
res.json = function() {
var args = arguments;
var body = args[0];
var status;
if (2 === args.length) {
// res.json(body, status) backwards compat
if ('number' === typeof args[1]) {
status = args[1];
} else {
status = body;
body = args[1];
}
}
resolveAsync(body, function(err, result) {
if (err) {
return next(err);
}
if (typeof status !== 'undefined') {
originalResJson(status, result);
} else {
originalResJson(result);
}
}, options.maxPromise, options);
};
}
if (~options.methods.indexOf('render')) {
var originalResRender = res.render.bind(res);
res.render = function(view, obj, fn) {
obj = obj || {};
if (arguments.length === 1) {
return originalResRender(view);
}
if (arguments.length === 2) {
if (typeof obj === 'function') {
return originalResRender(view, obj);
}
resolveAsync(obj, function(err, result) {
if (err) {
return next(err);
}
originalResRender(view, result);
}, options.maxPromise, options);
return;
}
resolveAsync(obj, function(err, result) {
if (err) {
return next(err);
}
originalResRender(view, result, fn);
}, options.maxPromise, options);
};
}
if (~options.methods.indexOf('send')) {
var originalResSend = res.send.bind(res);
res.send = function() {
var args = arguments;
var body = args[0];
var status;
if (2 === args.length) {
// res.send(body, status) backwards compat
if ('number' === typeof args[1]) {
status = args[1];
} else {
status = body;
body = args[1];
}
}
if (typeof body === 'object' && !(body instanceof Buffer)) {
resolveAsync(body, function(err, result) {
if (err) {
return next(err);
}
if (typeof status !== 'undefined') {
originalResSend(status, result);
} else {
originalResSend(result);
}
}, options.maxPromise, options);
} else {
if (status) {
originalResSend(status, body);
} else {
originalResSend(body);
}
}
};
}
next();
};
};
module.exports = expressPromise;