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;
}());
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 once 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.