【问题标题】:JavaScript Promises : Deep nested context with bind(this)JavaScript Promises : 使用 bind(this) 的深层嵌套上下文
【发布时间】:2016-04-16 13:59:58
【问题描述】:

因为我使用的原型具有调用同一原型中其他函数的函数,所以我必须使用 this 引用该方法

问题this创建:

但正因为如此,我必须保留一个使用 this 的上下文,这让我形成了非常丑陋的 .bind(this) 墙。

这是我为笑而制作的简化示例。

Killmyself.prototype.fireLeMissles = function () {

    return new Promise(function(resolve,reject) {
        this.anotherFunction(param).then(function(result) {

        someList.forEach(function(item) {
          this.fireLeMissles().then(function(anotherResult){
            promiseList.push(anotherResult)
          })
        },this);
        Promise.all(promiseList).then(function(promiseItem){
          childPlacesIds.forEach(function(childPlaceId) {
            //Do Other Stuff
          },this);
        });
      resolve(result);
    }.bind(this).catch(function(err){
      console.log("Yea, life sucks sometimes.")
    }));
  }.bind(this));
}

Killmyself.prototype.another = function(){
   //Other stuff
}

您可以看到,因为调用了同一原型中的函数,例如 this.anotherFunction(... 和 this.fireLeMissles(... 我必须深度保存上下文,现在(在我的更大版本中)使这段代码难以使用。

问题:

这是一个“习惯于 JavaScript 更难的方面”的事情吗?还是经验丰富的开发人员看到了可以避免像这样的深度绑定的简单方法?

【问题讨论】:

  • 如果nesting 高一级,我将使用.bnd(this),否则将使用var _this=this 并在嵌套函数中引用_this..
  • 既然你用 ES6 标记了这个,你熟悉箭头函数吗?
  • @loganfsmyth 我一直尝试坚持使用 es5,因为我从未想过将 babel 与服务器端编码一起使用,但我相信箭头函数的好处之一是上下文是隐含的。
  • 很高兴澄清一下,既然您标记了 ES6,我假设您要求的是 ES6 解决方案。 Node 4 和 5 原生支持箭头函数和 Promise,所以很多人使用它们。
  • @loganfsmyth 对不起,我也忘了标记 es5。我只是假设我将自己归类为一个糟糕的设计,或者为了这些目的而存在一个更巧妙的 es6 解决方案(我还没有深入研究)。

标签: javascript promise ecmascript-6 es6-promise


【解决方案1】:

如果您使用的是 ES6,您可以受益于 arrow functions,它保留了上下文。

var counter = function () {
    this.count = 0;
    setInterval( () => { // arrow function
        console.log(this.count++); // context is preserved
    }, 1000)
}
var counter = new counter();

所以,你的代码会变成这样:

Killmyself.prototype.fireLeMissles = function() {
    return new Promise((resolve, reject) => {
        this.anotherFunction(param).then(result => {
            someList.forEach(item => {
                this.fireLeMissles().then(anotherResult => {
                    promiseList.push(anotherResult)
                });
            });
            Promise.all(promiseList).then(promiseItem => {
                childPlacesIds.forEach(childPlaceId => {
                    //Do Other Stuff
                });
            });
            resolve(result);
        }).catch(err => {
            console.log("Yea, life sucks sometimes.")
        });
    });
}

对于 ES5,您可以像以前一样使用 .bind,也可以将 this 分配给函数中具有所需上下文的其他内容,然后在内部使用该变量内部函数。

Killmyself.prototype.fireLeMissles = function() {
    var self = this; /// use `self` instead of `this` from now on.
    return new Promise(function(resolve, reject) {
        self.anotherFunction(param).then(function(result) {
            someList.forEach(function(item) {
                self.fireLeMissles().then(function(anotherResult) {
                    promiseList.push(anotherResult)
                })
            });
            Promise.all(promiseList).then(function(promiseItem) {
                childPlacesIds.forEach(function(childPlaceId) {
                    //Do Other Stuff
                });
            });
            resolve(result);
        }).catch(function(err) {
            console.log("Yea, life sucks sometimes.")
        });
    });
}

【讨论】:

  • @Amir 使用 es6 和 node.js 服务器端编码很常见吗?每当我看到 es6 代码时,我总是会想到可以使用 es6 的前端编码,只要它与 babel 或其他等价物捆绑在一起即可。
  • 另外,感谢您提供非常有见地的帖子!在社区做出回应后,我一定会给予信任。 :)
  • @NickPineda 当然。我总是在我的节点项目中使用 es6。对于服务器端部分,节点本身支持这一点,并且由于所有内容都将在服务器上执行,因此您不必担心客户端的支持。如果你愿意在客户端使用 es6,我建议使用像 babeljs 这样的编译器。
  • @NickPineda 我在多个生产项目中使用过 es6。在客户端和服务器端。使用gruntgulp 或类似工具设计工作流程以将所有 es6 文件转换为 es5 以便交付生产,这并不难。
  • ES6 几个月前正式发布,目前还没有 100% 支持它,但足以回答这个问题。
【解决方案2】:

对于初学者,我不明白你在这里需要new Promise..,就像@loganfsmyth 所说,我会简单地使用箭头函数并降低复杂性:

Killmyself.prototype.fireLeMissles = function (param) {

  return this.anotherFunction(param)
  .then(someList => {
    var promiseList = someList.map( item => this.fireLeMissles(item));
    return Promise.all(promiseList);
  }).then(childPlacesIds => {
    childPlacesIds.forEach(childPlacesId = {
      // .... do something;
    });
    // return something.
  }).catch(err => console.log("Yea, life sucks sometimes."));

}

P。 S:我不确定这个param, someList, childPlacesIds 是从哪里来的,并假设您将promiseList 初始化为空数组。

【讨论】:

  • 是的,promiseList 将是一个简单的数组。我大量整理了原始代码,试图强调我正在处理的重复绑定问题。
  • 如果您的原始代码仍然是这样,请务必牢记new Promise
  • 这是正确的答案,重构了 [deferred antipattern](stackoverflow.com/questions/23803743/what-is-the-explicit-promise-construction-antipattern-and-how-do-我-避免-它)。
【解决方案3】:

Mido 的回答很好,我只是想提供一个替代方案,我认为这对了解会很有用 - 使用 Promise 作为代理:

Killmyself.prototype.fireLeMissles = function () {
  let fn = this.anotherFunction(param);
  let others = fn.then(_ => someList.map(this.fireLeMissles, this));
  let othersP = Promise.all(others);
  othersP.then(/* do OtherStuff */);
  return othersP; // or whatever its then returned
}

当然,使用像 bluebird 这样的库会更容易。

【讨论】:

  • 猜你把它进一步缩短为someList.map(this.fireLeMissles, this) :)
猜你喜欢
  • 2016-08-10
  • 2016-07-20
  • 1970-01-01
  • 2016-01-12
  • 2018-09-04
  • 1970-01-01
  • 2014-06-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多