├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── examples ├── mongoose │ ├── app.js │ ├── package.json │ ├── public │ │ └── stylesheets │ │ │ └── style.css │ ├── routes │ │ └── index.js │ └── views │ │ ├── index.jade │ │ └── layout.jade └── sequelize │ ├── app.js │ ├── package.json │ ├── public │ └── stylesheets │ │ └── style.css │ ├── routes │ └── index.js │ └── views │ ├── index.jade │ └── layout.jade ├── index.js ├── lib └── express_promise.js ├── package.json └── test ├── basic.js ├── json.js ├── recursive.js ├── render.js ├── send.js ├── skip_rule.js └── spec_helper.js /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | npm-debug.log 15 | node_modules 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | - "0.8" 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Zihua Li 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # express-promise 2 | An [express.js](http://expressjs.com) middleware for easy rendering async query. 3 | 4 | [![Build Status](https://travis-ci.org/luin/express-promise.png?branch=master)](https://travis-ci.org/luin/express-promise) 5 | 6 | ## Cases 7 | ### 1. previously 8 | 9 | ```javascript 10 | app.get('/users/:userId', function(req, res) { 11 | User.find(req.params.userId).then(function(user) { 12 | Project.getMemo(req.params.userId).then(function(memo) { 13 | res.json({ 14 | user: user, 15 | memo: memo 16 | }); 17 | }); 18 | }); 19 | }); 20 | ``` 21 | 22 | ### 1. now 23 | 24 | ```javascript 25 | app.get('/users/:userId', function(req, res) { 26 | res.json({ 27 | user: User.find(req.params.userId), 28 | memo: Project.getMemo(req.params.userId) 29 | }); 30 | }); 31 | ``` 32 | 33 | ### 2. previously 34 | 35 | ```javascript 36 | app.get('/project/:projectId', function(req, res) { 37 | var field = req.query.fields.split(';'); 38 | var result = {}; 39 | 40 | var pending = 0; 41 | if (field.indexOf('people') !== -1) { 42 | pending++; 43 | Project.getField(req.params.projectId).then(function(result) { 44 | result.people = result; 45 | if (--pending) { 46 | output(); 47 | } 48 | }); 49 | } 50 | 51 | if (field.indexOf('tasks') !== -1) { 52 | pending++; 53 | Project.getTaskCount(req.params.projectId).then(function(result) { 54 | result.tasksCount= result; 55 | if (--pending) { 56 | output(); 57 | } 58 | }); 59 | } 60 | 61 | function output() { 62 | res.json(result); 63 | } 64 | }); 65 | ``` 66 | 67 | ### 2. now 68 | ```javascript 69 | app.get('/project/:projectId', function(req, res) { 70 | var field = req.query.fields.split(';'); 71 | var result = {}; 72 | 73 | if (field.indexOf('people') !== -1) { 74 | result.people = Project.getField(req.params.projectId); 75 | } 76 | 77 | if (field.indexOf('tasks') !== -1) { 78 | result.tasksCount = Project.getTaskCount(req.params.projectId); 79 | } 80 | 81 | res.json(result); 82 | }); 83 | ``` 84 | 85 | ## Install 86 | $ npm install express-promise 87 | 88 | ## Usage 89 | Just `app.use` it! 90 | 91 | ```javascript 92 | app.use(require('express-promise')()); 93 | ``` 94 | 95 | This library supports the following methods: `res.send`, `res.json`, `res.render`. 96 | 97 | If you want to let express-promise support nodejs-style callbacks, you can use [dotQ](https://github.com/luin/dotQ) to convert the nodejs-style callbacks to Promises. For example: 98 | 99 | ```javascript 100 | require('dotq'); 101 | app.use(require('express-promise')()); 102 | 103 | var fs = require('fs'); 104 | app.get('/file', function(req, res) { 105 | res.send(fs.readFile.promise(__dirname + '/package.json', 'utf-8')); 106 | }); 107 | ``` 108 | 109 | ### Skip traverse 110 | 111 | As a gesture to performance, when traverse an object, we call `toJSON` on it to reduce the properties we need to traverse recursively. However that's measure has some negative effects. For instance, all the methods will be removed from the object so you can't use them in the template. 112 | 113 | If you want to skip calling `toJSON` on an object(as well as stop traverse it recursively), you can use the `skipTraverse` option. If the function return `true`, express-promise will skip the object. 114 | 115 | ```javascript 116 | app.use(require('express-promise')({ 117 | skipTraverse: function(object) { 118 | if (object.hasOwnProperty('method')) { 119 | return true; 120 | } 121 | } 122 | })) 123 | ``` 124 | 125 | ## Libraries 126 | express-promise works well with some ODM/ORM libraries such as [Mongoose](http://mongoosejs.com) and [Sequelize](http://sequelizejs.com). There are some examples in the /examples folder. 127 | 128 | ### Mongoose 129 | When query a document without passing a callback function, Mongoose will return a [Query](http://mongoosejs.com/docs/queries.html) instance. For example: 130 | 131 | ```javascript 132 | var Person = mongoose.model('Person', yourSchema); 133 | var query = Person.findOne({ 'name.last': 'Ghost' }, 'name occupation'); 134 | ``` 135 | 136 | Query has a `exec` method, when you call `query.exec(function(err, result) {})`, the query will execute and the result will return to the callback function. In some aspects, Query is like Promise, so express-promise supports Query as well. You can do this: 137 | 138 | ```javascript 139 | exports.index = function(req, res){ 140 | res.render('index', { 141 | title: 'Express', 142 | cat: Cat.findOne({name: 'Zildjian'}) 143 | }); 144 | }; 145 | ``` 146 | 147 | and in the index.jade, you can use `cat` directly: 148 | 149 | p The name of the cat is #{cat.name} 150 | 151 | ### Sequelize 152 | Sequelize supports Promise after version 1.7.0 :) 153 | 154 | ## Articles and Recipes 155 | * [Node Roundup: Bedecked, Knockout.sync.js, express-promise](http://dailyjs.com/2013/09/18/node-roundup/) 156 | * [减少异步嵌套,Express-promise](http://zihua.li/2013/09/express-promise/) [Chinese] 157 | 158 | ## License 159 | The MIT License (MIT) 160 | 161 | Copyright (c) 2013 Zihua Li 162 | 163 | Permission is hereby granted, free of charge, to any person obtaining a copy of 164 | this software and associated documentation files (the "Software"), to deal in 165 | the Software without restriction, including without limitation the rights to 166 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 167 | the Software, and to permit persons to whom the Software is furnished to do so, 168 | subject to the following conditions: 169 | 170 | The above copyright notice and this permission notice shall be included in all 171 | copies or substantial portions of the Software. 172 | 173 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 174 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 175 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 176 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 177 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 178 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 179 | 180 | 181 | [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/luin/express-promise/trend.png)](https://bitdeli.com/free "Bitdeli Badge") 182 | 183 | -------------------------------------------------------------------------------- /examples/mongoose/app.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var express = require('express') 7 | , routes = require('./routes') 8 | , http = require('http') 9 | , path = require('path'); 10 | 11 | var app = express(); 12 | 13 | app.configure(function(){ 14 | app.set('port', process.env.PORT || 3000); 15 | app.set('views', __dirname + '/views'); 16 | app.set('view engine', 'jade'); 17 | app.use(express.favicon()); 18 | app.use(express.logger('dev')); 19 | app.use(express.bodyParser()); 20 | app.use(express.methodOverride()); 21 | app.use(require('express-promise')()); 22 | app.use(app.router); 23 | app.use(express.static(path.join(__dirname, 'public'))); 24 | }); 25 | 26 | app.configure('development', function(){ 27 | app.use(express.errorHandler()); 28 | }); 29 | 30 | app.get('/', routes.index); 31 | 32 | http.createServer(app).listen(app.get('port'), function(){ 33 | console.log("Express server listening on port " + app.get('port')); 34 | }); 35 | -------------------------------------------------------------------------------- /examples/mongoose/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-promise-test-mongoose", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "start": "node app" 7 | }, 8 | "dependencies": { 9 | "express": "3.0.0rc2", 10 | "jade": "*", 11 | "mongoose": "~3.6.19", 12 | "express-promise": "~0.1.4" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/mongoose/public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } -------------------------------------------------------------------------------- /examples/mongoose/routes/index.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | mongoose.connect('mongodb://localhost/test'); 3 | 4 | var Cat = mongoose.model('Cat', { name: String }); 5 | 6 | // prepare data 7 | Cat.create({ name: 'Zildjian' }, function(err, result) { 8 | if (!err) { 9 | console.log('Created.'); 10 | } 11 | }); 12 | 13 | /* 14 | * GET home page. 15 | */ 16 | 17 | exports.index = function(req, res){ 18 | res.render('index', { title: 'Express', cat: Cat.findOne({name: 'Zildjian'})}); 19 | }; 20 | -------------------------------------------------------------------------------- /examples/mongoose/views/index.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | h1= title 5 | p Welcome to #{title} 6 | p The name of the cat is #{cat.name} 7 | -------------------------------------------------------------------------------- /examples/mongoose/views/layout.jade: -------------------------------------------------------------------------------- 1 | doctype 5 2 | html 3 | head 4 | title= title 5 | link(rel='stylesheet', href='/stylesheets/style.css') 6 | body 7 | block content -------------------------------------------------------------------------------- /examples/sequelize/app.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var express = require('express') 7 | , routes = require('./routes') 8 | , http = require('http') 9 | , path = require('path'); 10 | 11 | var app = express(); 12 | 13 | app.configure(function(){ 14 | app.set('port', process.env.PORT || 3000); 15 | app.set('views', __dirname + '/views'); 16 | app.set('view engine', 'jade'); 17 | app.use(express.favicon()); 18 | app.use(express.logger('dev')); 19 | app.use(express.bodyParser()); 20 | app.use(express.methodOverride()); 21 | app.use(require('express-promise')()); 22 | app.use(app.router); 23 | app.use(express.static(path.join(__dirname, 'public'))); 24 | }); 25 | 26 | app.configure('development', function(){ 27 | app.use(express.errorHandler()); 28 | }); 29 | 30 | app.get('/', routes.index); 31 | 32 | http.createServer(app).listen(app.get('port'), function(){ 33 | console.log("Express server listening on port " + app.get('port')); 34 | }); 35 | -------------------------------------------------------------------------------- /examples/sequelize/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "application-name", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "start": "node app" 7 | }, 8 | "dependencies": { 9 | "express": "3.0.0rc2", 10 | "jade": "*", 11 | "express-promise": "~0.1.5", 12 | "sequelize": "~2.0.0-beta.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/sequelize/public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } -------------------------------------------------------------------------------- /examples/sequelize/routes/index.js: -------------------------------------------------------------------------------- 1 | var Sequelize = require("sequelize"); 2 | 3 | var sequelize = new Sequelize('test', 'root', '', { 4 | host: "127.0.0.1", 5 | port: 3306 6 | }); 7 | 8 | 9 | var Project = sequelize.define('Project', { 10 | title: Sequelize.STRING 11 | }); 12 | 13 | sequelize.sync(); 14 | 15 | Project.create({ 16 | title: 'First project' 17 | }).done(function() { 18 | console.log('Created.'); 19 | }); 20 | 21 | /* 22 | * GET home page. 23 | */ 24 | 25 | exports.index = function(req, res){ 26 | console.log(typeof Project.find({where: {title: 'First project'}}).done); 27 | res.render('index', { title: 'Express', project: Project.find({where: {title: 'First project'}}) }); 28 | }; 29 | -------------------------------------------------------------------------------- /examples/sequelize/views/index.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | h1= title 5 | p Welcome to #{title} 6 | p Project title: #{project.title} 7 | -------------------------------------------------------------------------------- /examples/sequelize/views/layout.jade: -------------------------------------------------------------------------------- 1 | doctype 5 2 | html 3 | head 4 | title= title 5 | link(rel='stylesheet', href='/stylesheets/style.css') 6 | body 7 | block content -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/express_promise.js'); 2 | -------------------------------------------------------------------------------- /lib/express_promise.js: -------------------------------------------------------------------------------- 1 | var isPromise = function(v) { 2 | return v !== null && typeof v === 'object' && typeof v.then === 'function'; 3 | }; 4 | 5 | var isMongooseQuery = function(v) { 6 | return v !== null && typeof v === 'object' && typeof v.exec === 'function'; 7 | }; 8 | 9 | var resolveAsync = function(object, callback, count, options) { 10 | if (!object || typeof object !== 'object') { 11 | return callback(null, object); 12 | } 13 | 14 | if (count === 0) { 15 | return callback(new Error('Max promises (' + options.maxPromise + ') reached')); 16 | } 17 | 18 | if (isPromise(object)) { 19 | return object.then(function(result) { 20 | if (isPromise(result) || isMongooseQuery(result)) { 21 | resolveAsync(result, callback, count - 1, options); 22 | } else { 23 | callback(null, result); 24 | } 25 | }, function(err) { 26 | callback(err); 27 | }); 28 | } 29 | 30 | if (isMongooseQuery(object)) { 31 | return object.exec(function(err, result) { 32 | if (err) { 33 | callback(err); 34 | } 35 | if (isPromise(result) || isMongooseQuery(result)) { 36 | resolveAsync(result, callback, count - 1, options); 37 | } else { 38 | callback(err, result); 39 | } 40 | }); 41 | } 42 | 43 | if (options.skipTraverse && options.skipTraverse(object)) { 44 | return callback(null, object); 45 | } 46 | 47 | if (typeof object.toJSON === 'function') { 48 | object = object.toJSON(); 49 | if (!object || typeof object !== 'object') { 50 | return callback(null, object); 51 | } 52 | } 53 | 54 | var remains = []; 55 | Object.keys(object).forEach(function(key) { 56 | if (isPromise(object[key]) || isMongooseQuery(object[key])) { 57 | object[key].key = key; 58 | remains.push(object[key]); 59 | } else if (typeof object[key] === 'object') { 60 | remains.push({ 61 | key: key, 62 | }); 63 | } 64 | }); 65 | 66 | if (!remains.length) { 67 | return callback(null, object); 68 | } 69 | var pending = remains.length; 70 | 71 | remains.forEach(function(item) { 72 | function handleDone(err, result) { 73 | if (err) { 74 | return callback(err); 75 | } 76 | object[item.key] = result; 77 | if (--pending === 0) { 78 | callback(null, object); 79 | } 80 | } 81 | if (isPromise(item)) { 82 | item.then(function(result) { 83 | handleDone(null, result); 84 | }, function(err) { 85 | handleDone(err); 86 | }); 87 | } else if (isMongooseQuery(item)) { 88 | item.exec(function(err, result) { 89 | handleDone(err, result); 90 | }); 91 | } else { 92 | resolveAsync(object[item.key], handleDone, count - 1, options); 93 | } 94 | }); 95 | }; 96 | 97 | var expressPromise = function(options) { 98 | var defaultOptions = { 99 | methods: ['json', 'render', 'send'], 100 | maxPromise: 20 101 | }; 102 | 103 | options = options || {}; 104 | Object.keys(defaultOptions).forEach(function(key) { 105 | if (typeof options[key] === 'undefined') { 106 | options[key] = defaultOptions[key]; 107 | } 108 | }); 109 | 110 | return function(_, res, next) { 111 | if (typeof next !== 'function') { 112 | next = function() {}; 113 | } 114 | if (~options.methods.indexOf('json')) { 115 | var originalResJson = res.json.bind(res); 116 | res.json = function() { 117 | var args = arguments; 118 | var body = args[0]; 119 | var status; 120 | if (2 === args.length) { 121 | // res.json(body, status) backwards compat 122 | if ('number' === typeof args[1]) { 123 | status = args[1]; 124 | } else { 125 | status = body; 126 | body = args[1]; 127 | } 128 | } 129 | resolveAsync(body, function(err, result) { 130 | if (err) { 131 | return next(err); 132 | } 133 | if (typeof status !== 'undefined') { 134 | originalResJson(status, result); 135 | } else { 136 | originalResJson(result); 137 | } 138 | }, options.maxPromise, options); 139 | }; 140 | } 141 | 142 | if (~options.methods.indexOf('render')) { 143 | var originalResRender = res.render.bind(res); 144 | res.render = function(view, obj, fn) { 145 | obj = obj || {}; 146 | if (arguments.length === 1) { 147 | return originalResRender(view); 148 | } 149 | if (arguments.length === 2) { 150 | if (typeof obj === 'function') { 151 | return originalResRender(view, obj); 152 | } 153 | resolveAsync(obj, function(err, result) { 154 | if (err) { 155 | return next(err); 156 | } 157 | originalResRender(view, result); 158 | }, options.maxPromise, options); 159 | return; 160 | } 161 | resolveAsync(obj, function(err, result) { 162 | if (err) { 163 | return next(err); 164 | } 165 | originalResRender(view, result, fn); 166 | }, options.maxPromise, options); 167 | }; 168 | } 169 | 170 | if (~options.methods.indexOf('send')) { 171 | var originalResSend = res.send.bind(res); 172 | res.send = function() { 173 | var args = arguments; 174 | var body = args[0]; 175 | var status; 176 | if (2 === args.length) { 177 | // res.send(body, status) backwards compat 178 | if ('number' === typeof args[1]) { 179 | status = args[1]; 180 | } else { 181 | status = body; 182 | body = args[1]; 183 | } 184 | } 185 | if (typeof body === 'object' && !(body instanceof Buffer)) { 186 | resolveAsync(body, function(err, result) { 187 | if (err) { 188 | return next(err); 189 | } 190 | if (typeof status !== 'undefined') { 191 | res.status(status); 192 | originalResSend(result); 193 | } else { 194 | originalResSend(result); 195 | } 196 | }, options.maxPromise, options); 197 | } else { 198 | if (status) { 199 | res.status(status); 200 | originalResSend(body); 201 | } else { 202 | originalResSend(body); 203 | } 204 | } 205 | }; 206 | } 207 | next(); 208 | }; 209 | }; 210 | 211 | module.exports = expressPromise; 212 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-promise", 3 | "version": "0.5.0", 4 | "scripts": { 5 | "test": "mocha -R spec" 6 | }, 7 | "main": "./", 8 | "licenses": "MIT", 9 | "homepage": "https://github.com/luin/express-promise", 10 | "repository": { 11 | "type": "git", 12 | "url": "git://github.com/luin/express-promise.git" 13 | }, 14 | "engines": [ 15 | "node >=0.6.0" 16 | ], 17 | "keywords": [ 18 | "promise", 19 | "express", 20 | "middleware" 21 | ], 22 | "devDependencies": { 23 | "mocha": "~1.12.1", 24 | "should": "~1.2.2", 25 | "dotq": "~0.1.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/basic.js: -------------------------------------------------------------------------------- 1 | require('./spec_helper'); 2 | var expressPromise = require('..'); 3 | 4 | describe('basic', function() { 5 | it('should use the toJSON method', function(done) { 6 | var res = { 7 | json: function(body) { 8 | body.a.b.should.equal('hi'); 9 | body.a.c.should.not.have.property('d'); 10 | body.a.c.f.should.equal('hi'); 11 | done(); 12 | } 13 | }; 14 | expressPromise({methods: ['json']})(null, res); 15 | 16 | function async(callback) { 17 | callback(null, 'hi'); 18 | } 19 | 20 | res.json({ 21 | a: { 22 | b: async.promise(), 23 | c: { 24 | d: async.promise(), 25 | toJSON: function() { 26 | return { 27 | f: 'hi' 28 | }; 29 | } 30 | } 31 | } 32 | }); 33 | }); 34 | 35 | }); 36 | -------------------------------------------------------------------------------- /test/json.js: -------------------------------------------------------------------------------- 1 | require('./spec_helper'); 2 | var expressPromise = require('..'); 3 | 4 | describe('json', function() { 5 | it('should work well without Promise', function(done) { 6 | var res = { 7 | json: function(body) { 8 | arguments.should.have.length(1); 9 | body.should.equal('hi'); 10 | done(); 11 | } 12 | }; 13 | expressPromise({methods: ['json']})(null, res); 14 | 15 | res.json('hi'); 16 | }); 17 | 18 | it('should support Promise', function(done) { 19 | var res = { 20 | json: function(body) { 21 | arguments.should.have.length(1); 22 | body.promise.should.equal('hi'); 23 | done(); 24 | } 25 | }; 26 | expressPromise({methods: ['json']})(null, res); 27 | 28 | function async(callback) { 29 | callback(null, 'hi'); 30 | } 31 | 32 | res.json({ 33 | promise: async.promise() 34 | }); 35 | }); 36 | 37 | it('should support two arguments', function(done) { 38 | var res = { 39 | json: function(status, body) { 40 | arguments.should.have.length(2); 41 | status.should.equal(200); 42 | body.promise.should.equal('hi'); 43 | done(); 44 | } 45 | }; 46 | expressPromise({methods: ['json']})(null, res); 47 | 48 | function async(callback) { 49 | callback(null, 'hi'); 50 | } 51 | 52 | res.json(200, { 53 | promise: async.promise() 54 | }); 55 | }); 56 | }); 57 | 58 | -------------------------------------------------------------------------------- /test/recursive.js: -------------------------------------------------------------------------------- 1 | require('./spec_helper'); 2 | var expressPromise = require('..'); 3 | 4 | describe('recursive', function() { 5 | it('should resolve a promise that resolves with a promise', function(done) { 6 | var res = { 7 | json: function(body) { 8 | body.a.should.equal('hi'); 9 | done(); 10 | } 11 | }; 12 | 13 | expressPromise({methods: ['json']})(null, res); 14 | function async(callback) { 15 | callback(null, 'hi'); 16 | } 17 | function doubleAsync(callback) { 18 | callback(null, async.promise()); 19 | } 20 | 21 | res.json({ 22 | a: doubleAsync.promise() 23 | }); 24 | 25 | }); 26 | 27 | }); 28 | 29 | 30 | -------------------------------------------------------------------------------- /test/render.js: -------------------------------------------------------------------------------- 1 | require('./spec_helper'); 2 | var expressPromise = require('..'); 3 | 4 | describe('render', function() { 5 | it('should work well with one param', function(done) { 6 | var res = { 7 | render: function(view) { 8 | arguments.should.have.length(1); 9 | view.should.equal('index'); 10 | done(); 11 | } 12 | }; 13 | expressPromise({methods: ['render']})(null, res); 14 | 15 | res.render('index'); 16 | }); 17 | 18 | it('should work well with two params and callback', function(done) { 19 | var res = { 20 | render: function(view, callback) { 21 | arguments.should.have.length(2); 22 | view.should.equal('index'); 23 | callback().should.equal('test'); 24 | done(); 25 | } 26 | }; 27 | expressPromise({methods: ['render']})(null, res); 28 | 29 | res.render('index', function() { 30 | return 'test'; 31 | }); 32 | }); 33 | 34 | it('should work well with two params and locals', function(done) { 35 | var res = { 36 | render: function(view, locals) { 37 | arguments.should.have.length(2); 38 | view.should.equal('index'); 39 | locals.promise.should.equal('hi'); 40 | done(); 41 | } 42 | }; 43 | expressPromise({methods: ['render']})(null, res); 44 | 45 | function async(callback) { 46 | callback(null, 'hi'); 47 | } 48 | 49 | res.render('index', { 50 | promise: async.promise() 51 | }); 52 | }); 53 | 54 | it('should work well with three params', function(done) { 55 | var res = { 56 | render: function(view, locals, callback) { 57 | arguments.should.have.length(3); 58 | view.should.equal('index'); 59 | locals.promise.should.equal('hi'); 60 | callback().should.equal('test'); 61 | done(); 62 | } 63 | }; 64 | expressPromise({methods: ['render']})(null, res); 65 | 66 | function async(callback) { 67 | callback(null, 'hi'); 68 | } 69 | 70 | res.render('index', { 71 | promise: async.promise() 72 | }, function() { 73 | return 'test'; 74 | }); 75 | }); 76 | }); 77 | 78 | 79 | -------------------------------------------------------------------------------- /test/send.js: -------------------------------------------------------------------------------- 1 | require('./spec_helper'); 2 | var expressPromise = require('..'); 3 | 4 | describe('send', function() { 5 | it('should support string body', function(done) { 6 | var res = { 7 | send: function(body) { 8 | arguments.should.have.length(1); 9 | body.should.equal('hi'); 10 | done(); 11 | } 12 | }; 13 | expressPromise({methods: ['send']})(null, res); 14 | 15 | res.send('hi'); 16 | }); 17 | 18 | it('should support buffer body', function(done) { 19 | var res = { 20 | send: function(body) { 21 | arguments.should.have.length(1); 22 | body.toString().should.equal('hi'); 23 | done(); 24 | } 25 | }; 26 | expressPromise({methods: ['send']})(null, res); 27 | 28 | res.send(new Buffer('hi')); 29 | }); 30 | 31 | it('should support promise body', function(done) { 32 | var res = { 33 | send: function(body) { 34 | arguments.should.have.length(1); 35 | body.should.equal('hi'); 36 | done(); 37 | } 38 | }; 39 | expressPromise({methods: ['send']})(null, res); 40 | 41 | function async(callback) { 42 | callback(null, 'hi'); 43 | } 44 | 45 | res.send(async.promise()); 46 | }); 47 | 48 | it('should work well with two params with promise', function(done) { 49 | var pass = 0; 50 | var res = { 51 | status: function(status) { 52 | arguments.should.have.length(1); 53 | status.should.equal(200); 54 | if (++pass === 2) { 55 | done(); 56 | } 57 | }, 58 | send: function(body) { 59 | arguments.should.have.length(1); 60 | body.promise.should.equal('hi'); 61 | if (++pass === 2) { 62 | done(); 63 | } 64 | } 65 | }; 66 | expressPromise({methods: ['send']})(null, res); 67 | 68 | function async(callback) { 69 | callback(null, 'hi'); 70 | } 71 | 72 | res.send(200, { 73 | promise: async.promise() 74 | }); 75 | }); 76 | 77 | }); 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /test/skip_rule.js: -------------------------------------------------------------------------------- 1 | require('./spec_helper'); 2 | var expressPromise = require('..'); 3 | 4 | describe('skip rule', function() { 5 | it('should skip traverse the object', function(done) { 6 | var res = { 7 | json: function(body) { 8 | body.a.b.should.equal('hi'); 9 | body.a.c.name.should.equal('lib'); 10 | done(); 11 | } 12 | }; 13 | expressPromise({methods: ['json'], skipTraverse: function(object) { 14 | return object.hasOwnProperty('name'); 15 | }})(null, res); 16 | 17 | function async(callback) { 18 | callback(null, 'hi'); 19 | } 20 | 21 | res.json({ 22 | a: { 23 | b: async.promise(), 24 | c: { 25 | name: 'lib', 26 | d: async.promise(), 27 | toJSON: function() { 28 | return { 29 | f: 'hi' 30 | }; 31 | } 32 | } 33 | } 34 | }); 35 | }); 36 | 37 | }); 38 | -------------------------------------------------------------------------------- /test/spec_helper.js: -------------------------------------------------------------------------------- 1 | require('should'); 2 | require('dotq'); 3 | --------------------------------------------------------------------------------