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;
|