Express 4 - 将 res.json 与 promise.then 链接不起作用

3

我正在开发一个使用mysqlsequelize包的express 4应用程序。Sequelize ORM使用promises从数据库中获取数据。我试图在路由器中获取数据并发送JSON响应。当我尝试使用then回调函数链接promise和res.json时,控制台会报错,显示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);
});

错误堆栈:

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;

}());
1个回答

18
当您将`res.json`作为一个函数传递时,`res`对象会丢失,因此当执行`json()`时,它没有对象,这就是您看到的错误。您可以通过使用`.bind()`来解决这个问题:

当您将res.json作为一个函数传递时,res对象会丢失,因此当执行json()时,它没有对象,这就是您看到的错误。您可以通过使用.bind()来解决这个问题:

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

这将确保在方法执行时,res 对象与你的方法一起保持。使用上述的 .bind() 本质上和下面这行代码相同:

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

实际上,.bind() 实际上创建了一个桩函数,就像上面例子中的匿名函数一样。 它只是为您执行此操作,而不是让您自己执行。


进一步举例说明,假设您有两个单独的 res 对象,分别是来自于两个不同请求的 res1res2

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

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

这是因为引用 res1.json 只是获取了对 .json 方法的引用。它使用 res1 来获取该方法(该方法从 res1 原型中获取,但一旦拥有该方法,它只是指向该方法的指针,不再与包含该方法的对象相关联。因此,当您将 res.json 传递给函数时,您不会得到与 res 的关联。然后,当您传递 res.json 到实际调用您的函数的函数时,它会这样调用:
var z = res.json;
z();

而当调用z()时,json内部的this值最终变为undefined,并且与res对象没有关联。使用.bind()创建一个存根函数,以res.json(...)的方式调用它,保持连接到对象,并确保在执行方法时this被正确设置。


只是为了确保我理解。res未定义,因为传递res.json而没有使用()进行存储,但不会立即调用,由于调用站点确定此绑定,因此我们得到未定义? - Yasin Yaqoobi
什么是存根函数? - d9ngle
1
@d9ngle - 存根函数只是一个小的短函数,它包装另一个函数并对调用包装函数的方式进行轻微调整。您可以在MDN上查看.bind()的polyfill 此处以了解其工作原理的示例。 - jfriend00

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接