在javascript中使用promises和原型时的优雅回调绑定

时间:2014-09-26 09:22:27

标签: javascript prototype promise

我是一个沉重的javascript原型并承诺用户。

我的问题是,每次认证我的承诺时,我都需要使用.bind(this)设置正确的上下文。

以下是显示问题的示例代码(jsbin):

var Q = require('Q');

var AsyncCounter = function(initialValue){
  this.value = initialValue;
};

AsyncCounter.prototype.increment=function(){
  return Q.fcall(function(){
    return ++this.value;
  }.bind(this));
};

AsyncCounter.prototype.decrement=function(){
  return Q.fcall(function(){
    return --this.value;
  }.bind(this));
};

var counter = new AsyncCounter(10);

counter.increment()
  .then(function(incrementedValue){
    console.log('incremented value:', incrementedValue)
  })
  .then(counter.decrement.bind(counter))
  .then(function(decrementedValue){
    console.log('decremented value:', decrementedValue)
  });

了解我必须依赖bind()的频率?我发现它太不优雅了。

我确实知道petkaantonov/bluebird promise库及其非常有用的bind(scope)函数,它在回调上传播范围,但我觉得有更好的 native 方法可以做它

有人有正确的方法吗?

4 个答案:

答案 0 :(得分:4)

请记住,当您了解Foo.prototype.bar=function(){...}时,又拒绝将构造函数中的方法定义为this.bar = function(){...}?原因是速度和记忆效率。

现在你有充分的理由将时钟倒转并牺牲速度/效率,以实现“可拆卸”#34;方法 - 即特定于其实例的构造函数的方法,this准备好绑定,这正是您想要的。

如果多次调用这些方法,那么在引用该函数时不必使用bind()就可以补偿构造的低效率。此外,您还可以获得所寻求的语法便利。

这里是您的示例版本,为了更好地展示语法的便利性而重新编写:

var AsyncCounter = function(value){

    this.increment = function(){
      return Q.fcall(function(){
        return console.log("++value: " + ++value);
      }.bind(this));
    }.bind(this);

    this.decrement = function(){
      return Q.fcall(function(){
        return console.log("--value: " + --value);
      }.bind(this));
    }.bind(this);
};

var counter = new AsyncCounter(10);

counter.increment()
  .then(counter.decrement)
  .then(counter.increment);

总之,您需要做两件事:

  • 在构造函数
  • 中定义方法
  • this绑定到方法。

答案 1 :(得分:1)

我认为另一种方法是利用闭包。 promise中使用的每个回调都可以包含对创建它的根作用域的某些变量的引用。

通常,大多数人所做的是创建一个名为self的变量,该变量包含对外部this的引用。因此,只要执行promise的函数,闭包就会保持引用,从而能够访问范围。

AsyncCounter.prototype.increment=function(){
   var self = this;
   return Q.fcall(function(){
    return ++self .value;
  });
};
老实说,我不确定哪一个更优雅。我不确定是否有其他方法可以在不使用此技术或bind方法的情况下执行您想要的操作。

答案 2 :(得分:1)

我认为最好的解决方案是在承诺中添加context方法,以便在将来的this调用中为函数调用指定then。这似乎是我在一些图书馆看到过的,不记得在哪里,也是我自己实现的。想法是写

promise.context(this).then(myfunc)

我不知道Q,所以我不知道扩展它/定制它是否容易甚至可能有这种行为。这是使用原生JS承诺的这个想法的玩具实现:

Promise.prototype.context = function (ctxt) { this.ctxt = ctxt; return this; }

Promise.prototype.then = (function() {
    var old_then = Promise.prototype.then;
    return function(fulfill, reject) {
        return old_then.call(this,
            fulfill && fulfill.bind && fulfill.bind(this.ctxt),
            reject  && reject.bind  && reject.bind(this.ctxt)
        );
    };
}());

实际上,您通常可以编写代码,这样就不会在context次调用中出现问题,只需在创建承诺时执行一次。所以:

var MyTrip = {
    gotoMars: function() { return InterPlanetaryTrip(Mars).context(this); },
    report:   function() { console.log("I'm back, Mom!"); },
    go:       function() { gotoMars.then(this.report).then(this.drink); }
};

这种方法为我带来了无限的恶化,并且我可以看到零缺点。我看不到更多的本地人#34;这样做的方式,无论那可能意味着什么。

减少干扰的方法?

作为对你问题的一个更直接的回答,你可能会引入一点糖 - 一种方法,一举绑定并调用Q.fcall,如下:

AsyncCounter.prototype.fcall = function(fn) { 
    return Q.fcall(fn.bind(this)); 
};

AsyncCounter.prototype.increment = function() { 
    return this.fcall(function() {
        return ++this.value;
    });
};

答案 3 :(得分:1)

如果你正在使用ES6,你可以使用箭头功能并使用词法范围。

AsyncCounter.prototype.increment = () => {
    return Q.fcall(() => ++this.value);
};