为承诺链导出快速路由方法的最佳方式?

Best way to export Express route methods for promise chains?

我有一个正在重构的 API 路由以使用 ES6 承诺来避免回调地狱。

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

路由文件:

函数文件:

这很好用。但是,我 喜欢 要做的是将 Class 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 classes,另一种执行相同行为的方法只是稍微清理一下代码,是导出一个匿名函数,如 Nick Panov here: In Node.js, how do I "include" functions from my other files?[=30 所述=]

函数文件:

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()... 标记每个方法,但正如我最初想要的那样,它确实在路由文件中采取了额外的步骤 - 这样做可以避免我必须分配一个特定的方法的命名空间。

路线文件

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

函数被重置以反映每个新的 reqres 对象,因为 POST 请求命中路由,并且 this 关键字显然绑定到 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 from:

提供

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

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
  }
}

显然,这更符合我最初的要求,因为它增强了导出代码的整体可读性 BUT 这样做将要求您像我最初那样为路由文件中的新 class 分配一个命名空间:

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

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

提议/实验特征:

这不是我真正的驾驶室,但根据 Bergi, there is currently a Stage-2 proposal (https://github.com/tc39/proposal-class-public-fields) 试图将 "class instance 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 问题将消失,并且可以选择自动绑定方法。

我的(有限)理解是箭头函数可以用来完成这个,像这样:

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

显然这现在可以使用 Babel 编译器来完成,但显然是实验性的并且有风险。另外,在这种情况下,我们尝试在 Node / Express 中执行此操作,这几乎是一个有争议的问题:)