Chaitanya Chandurkar Chaitanya Chandurkar - 5 months ago 73
Node.js Question

Express 4 - chaining res.json with promise.then does not work

I'm working on an express 4 app that uses

mysql
and
sequelize
packages. Sequelize ORM uses promises to fetch data from database. I'm trying to fetch data in router and send json response. When I try to chain
then
callback of promise with
res.json
I get an error in console saying
Unhandled rejection TypeError: Cannot read property 'get' of undefined


// This works
employeeRouter.get("/:id", function(req, res){
Employee.findById(req.params.id).then(function(data){
res.json(data);
});
});

// Replacing above code with following doesn't work
employeeRouter.get("/:id", function(req, res){
Employee.findById(req.params.id).then(res.json);
});


Error Stack:

Unhandled rejection TypeError: Cannot read property 'get' of undefined
at json (D:\Workstation\DataPro\CountryStats\node_modules\express\lib\response.js:241:21)
at tryCatcher (D:\Workstation\DataPro\CountryStats\node_modules\bluebird\js\release\util.js:16:23)
at Promise._settlePromiseFromHandler (D:\Workstation\DataPro\CountryStats\node_modules\bluebird\js\release\promise.js:504:31)
at Promise._settlePromise (D:\Workstation\DataPro\CountryStats\node_modules\bluebird\js\release\promise.js:561:18)
at Promise._settlePromise0 (D:\Workstation\DataPro\CountryStats\node_modules\bluebird\js\release\promise.js:606:10)
at Promise._settlePromises (D:\Workstation\DataPro\CountryStats\node_modules\bluebird\js\release\promise.js:685:18)
at Async._drainQueue (D:\Workstation\DataPro\CountryStats\node_modules\bluebird\js\release\async.js:138:16)
at Async._drainQueues (D:\Workstation\DataPro\CountryStats\node_modules\bluebird\js\release\async.js:148:10)
at Immediate.Async.drainQueues [as _onImmediate] (D:\Workstation\DataPro\CountryStats\node_modules\bluebird\js\release\async.js:17:14)
at processImmediate [as _immediateCallback] (timers.js:383:17)


models/employee.js

var Sequelize = require('sequelize'),
sequelize = require('../db-connect/sequelize');

(function(){

// Use Strict Linting
'use strict';

// Define Sequalize
var Employee = sequelize.define('employee', {
empNo: { field: 'emp_no', type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true },
birthDate: { field: 'birth_date', type: Sequelize.DATE },
firstName: { field: 'first_name', type: Sequelize.STRING },
lastName: { field: 'last_name', type: Sequelize.STRING },
gender: { field: 'gender', type: Sequelize.ENUM('M', 'F') },
hireDate: { field: 'hire_date', type: Sequelize.DATE },
});

// Export
module.exports = Employee;

}());


db-connect/sequelize.js

var Sequelize = require('sequelize');

(function(){

// Use Strict Linting
'use strict';

// Sequalize Connection
var sequelize = null;

// Create Sequalize Connection
if(!sequelize){
sequelize = new Sequelize('employees', 'root', '', {
host: 'localhost',
dialect: 'mysql',
define: {
timestamps: false
}
});
}

module.exports = sequelize;

}());


routes/employees.js

var express = require('express'),
Employee = require('../models/employee');

(function(app){

// Use Strict Linting
'use strict';

// Create Router
var employeeRouter = express.Router();

// Home Page
employeeRouter.get("/", function(req, res){
res.json({employees: ['all']});
});

// Get Specific Employee
employeeRouter.get("/:id", function(req, res, next){
Employee.findById(req.params.id).then(function(data){
res.json(data);
});
});

// ----------------------------------
// Export
// ----------------------------------

module.exports = employeeRouter;

}());

Answer

When you pass res.json as a function, the res object gets lost and thus when json() executes, it has no object and you get the error you are seeing. You can fix that by using .bind():

employeeRouter.get("/:id", function(req, res){
   Employee.findById(req.params.id).then(res.json.bind(res));
});

This will make sure that the res object stays with your method when the method is executed. Using .bind() as above is essentially the same as:

employeeRouter.get("/:id", function(req, res){
   Employee.findById(req.params.id).then(function(data) {
       return res.json(data);
   });
});

In fact, .bind() actually creates a stub function like the anonymous one in the above example. It just does it for you rather than making you do it.


To further example, let's say you had two separate res objects, res1 and res2 from two separate requests.

var x = res1.json;
var y = res2.json;

console.log(x === y);    // true, no association with either res1 or res2 any more

This is because referencing res1.json just gets a reference to the .json method. It uses res1 to get that method (which is fetched from the res1 prototype, but one it has the method, it's just a pointer to the method and there is no longer an association with the object that contained the method. So, when you pass res.json to a function, you get no attachment to res. Then when the function you passed res.json to goes to actually call your function it calls it like this:

var z = res.json;
z();

And, when z() is called, the this value inside of json ends up being undefined and there is no connection to the res object. Using .bind() creates a stub function that calls it as res.json(...) to keep the connection to the object and make sure this is set properly when the method is executed.