javascript - 为 promise 链导出 Express 路由方法的最佳方式?

标签 javascript node.js express es6-promise es6-class

我正在重构一个 API 路由,以使用 ES6 Promise 来避免回调 hell 。

成功转换为 promise 链后,我想将我的 .then() 函数导出到一个单独的文件中,以保持整洁和清晰。

路由文件: enter image description here

函数文件: enter image description here

这很好用。但是,我喜欢要做的是将类constructor()函数中声明的函数移动到独立的方法中,这些方法可以引用由构造函数实例化的值。这样读起来就更好了。

但是,当我这样做时,我遇到了范围界定问题 - this 未定义,等等。执行此操作的正确方法是什么? ES6 适合在这里使用,还是应该使用其他结构?

原始代码:

路线...

.post((req, res) => {

  let SubmitRouteFunctions = require('./functions/submitFunctions.js');
  let fn = new SubmitRouteFunctions(req, res);

  // *******************************************
  // ***** THIS IS WHERE THE MAGIC HAPPENS *****
  // *******************************************
  Promise.all([fn.redundancyCheck, fn.getLocationInfo])
         .then(fn.resetRedundantID)
         .then(fn.constructSurveyResult)
         .then(fn.storeResultInDB)
         .then(fn.redirectToUniqueURL)
         .catch((err) => {
           console.log(err);
           res.send("ERROR SUBMITTING YOUR RESULT: ", err);
         });
  })

导出函数...

module.exports = class SubmitRouteFunctions {

   constructor (req, res) {
this.res = res;
this.initialData = {
  answers    : req.body.responses,
  coreFit    : req.body.coreFit,
  secondFit  : req.body.secondFit,
  modules    : req.body.modules,
};

this.newId = shortid.generate();
this.visitor = ua('UA-83723251-1', this.newId, {strictCidFormat: false}).debug();
this.clientIp = requestIp.getClientIp(req);

this.redundancyCheck = mongoose.model('Result').findOne({quizId: this.newId});
this.getLocationInfo = request.get('http://freegeoip.net/json/' + this.clientIp).catch((err) => err);

this.resetRedundantID = ([mongooseResult, clientLocationPromise]) => {

    console.log(mongooseResult);
    if (mongooseResult != null) {
      console.log('REDUNDANT ID FOUND - GENERATING NEW ONE')
      this.newId = shortid.generate();
      this.visitor = ua('UA-83723251-1', this.newId, {strictCidFormat: false});
      console.log('NEW ID: ', this.newId);
    };
    return clientLocationPromise.data;
  }

this.constructSurveyResult = (clientLocation) => {
    let additionalData = {quizId: this.newId, location: clientLocation};
    return Object.assign({}, this.initialData, additionalData);
  }

this.storeResultInDB = (newResult) => mongoose.model('Result').create(newResult).then((result) => result).catch((err) => err);

this.redirectToUniqueURL = (mongooseResult) => {
  let parsedId = '?' + queryString.stringify({id: mongooseResult.quizId});
  let customUrl = 'http://explore-your-fit.herokuapp.com/results' + parsedId;
  this.res.send('/results' + parsedId);
}
  }
}

最佳答案

替代#1:

与使用 ES6 类不同,执行相同行为并稍微清理代码的另一种方法是导出匿名函数,如 Nick Panov 中所述。这里:In Node.js, how do I "include" functions from my other files?

函数文件:

module.exports = function (req, res) {

    this.initialData = {
      answers    : req.body.responses,
      coreFit    : req.body.coreFit,
      secondFit  : req.body.secondFit,
      modules    : req.body.modules,
    };

    this.newId = shortid.generate();
    this.visitor = ua('UA-83723251-1', this.newId, {strictCidFormat: false}).debug();
    this.clientIp = requestIp.getClientIp(req);

    this.redundancyCheck = mongoose.model('Result').findOne({quizId: this.newId});
    this.getLocationInfo = request.get('http://freegeoip.net/json/' + this.clientIp).catch((err) => err);

    this.resetRedundantID = ([mongooseResult, clientLocationPromise]) => {
        if (mongooseResult != null) {
          console.log('REDUNDANT ID FOUND - GENERATING NEW ONE')
          this.newId = shortid.generate();
          this.visitor = ua('UA-83723251-1', this.newId, {strictCidFormat: false});
          console.log('NEW ID: ', this.newId);
        };
        return clientLocationPromise.data;
      }

    this.constructSurveyResult = (clientLocation) => {
        let additionalData = {quizId: this.newId, location: clientLocation};
        return Object.assign({}, this.initialData, additionalData);
      }

    this.storeResultInDB = (newResult) => mongoose.model('Result').create(newResult).then((result) => result).catch((err) => err);

    this.redirectToUniqueURL = (mongooseResult) => {
      let parsedId = '?' + queryString.stringify({id: mongooseResult.quizId});
      let customUrl = 'http://explore-your-fit.herokuapp.com/results' + parsedId;
      res.send('/results' + parsedId);
    }
}

尽管这并不能避免用 this.someFn()... 标记每个方法正如我最初想要的那样,它确实在路由文件中采取了额外的步骤 - 以这种方式执行操作可以使我不必为方法分配特定的 namespace 。

路线文件

.post((req, res) => {
          require('./functions/submitFunctions_2.js')(req, res);

          Promise.all([redundancyCheck, getLocationInfo])
                 .then(resetRedundantID)
                 .then(constructSurveyResult)
                 .then(storeResultInDB)
                 .then(redirectToUniqueURL)
                 .catch((err) => {
                   console.log(err);
                   res.send("ERROR SUBMITTING YOUR RESULT: ", err);
                 });
      })

功能被重置以反射(reflect)每个新的 reqres对象作为 POST 请求到达路由,并且 this关键字显然绑定(bind)到每个导入方法中的 POST 路由回调。

重要提示:无法使用此方法导出箭头函数。导出的函数必须是传统的匿名函数。这就是原因,根据 Udo G对同一主题的评论:

It should be worth to note that this works because this in a function is the global scope when the function is called directly (not bound in any way).

替代#2:

另一种选择,由 Bergi 提供来自:How to use arrow functions (public class fields) as class methods?

我真正寻找的是一个实验性功能......

There is an proposal which might allow you to omit the constructor() and directly put the assignment in the class scope with the same functionality, but I wouldn't recommend to use that as it's highly experimental.

但是,仍然有一种方法可以分离方法:

Alternatively, you can always use .bind, which allows you to declare the method on the prototype and then bind it to the instance in the constructor. This approach has greater flexibility as it allows modifying the method from the outside of your class.

基于 Bergi 的示例:

module.exports = class SomeClass {

  constructor() {
    this.someMethod= this.someMethod.bind(this);
    this.someOtherMethod= this.someOtherMethod.bind(this);
    …
  }

  someMethod(val) {
    // Do something with val
  }

  someOtherMethod(val2) {
    // Do something with val2
  }
}

显然,这更符合我最初的需求,因为它增强了导出代码的整体可读性 但是

let SubmitRouteFunctions = require('./functions/submitFunctions.js');
let fn = new SubmitRouteFunctions(req, res);

Promise.all([fn.redundancyCheck, fn.getLocationInfo])
       .then(...)

提议/实验功能:

这并不是我真正的驾驶室,但根据Bergi ,目前有一个 Stage-2 提案 ( https://github.com/tc39/proposal-class-public-fields ) 正在尝试将“类实例字段”添加到下一个 ES 规范中。

"Class instance fields" describe properties intended to exist on instances of a class (and may optionally include initializer expressions for said properties)

据我了解,这将通过允许附加到 class 的方法来完全解决这里描述的问题对象来引用其自身的每个实例。因此,this问题就会消失,并且可以选择自动绑定(bind)方法。

(有限)的理解是箭头函数将用于完成此操作,如下所示:

  class SomeClass {
      constructor() {...}
      someMethod (val) => {
        // Do something with val
        // Where 'this' is bound to the current instance of SomeClass
      }
    }

显然,现在可以使用 Babel 编译器来完成此操作,但显然是实验性的且有风险。另外,在本例中,我们尝试在 Node/Express 中执行此操作,这使得这几乎成为一个没有实际意义的问题:)

关于javascript - 为 promise 链导出 Express 路由方法的最佳方式?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44379482/

相关文章:

javascript - 是否可以重新排序 node.js 事件循环?

javascript - Express 根据当前加载的页面设置样式表的条件加载

mysql - javascript中父级回调之前的多个嵌套回调函数

node.js - KeystoneJS watch :lint is not in your gulpfile

node.js - 如何确定 Node.js 发送 HTTP 响应正文所花费的时间?

javascript - 如何以 Rails 方式处理 JavaScript 事件(例如 'link_to :remote' )?

javascript - 如何选择不包含某个子元素的元素?

javascript - getElementById 的非常简单的 JavaScript 示例。为什么我收到错误?

javascript - 是否可以使用整个宽度在 A4 纸上打印 div 的内容?

node.js - 当 Graphite 位于不同服务器上时,Stats.d 会抛出 "connect ECONNREFUSED"错误