Elegant callback binding when using promises and prototypes in javascript

I am a heavy javascript prototypes and promises user.

My problem is that I need to use .bind(this)

to set the correct context every time I pass my promises.

Here is some sample code showing the problem ( 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)
  });

      

See how often I have to rely on bind()

? I find it too elusive.

I am aware of the petkaantonov / bluebird promise library and its very useful ones bind(scope)

that extend scope to callbacks, but I feel there is a better, native way to do this.

Does anyone have a correct way to do this?

+3


source to share


4 answers


Remember when you found out about Foo.prototype.bar=function(){...}

and turned your back on defining methods inside a constructor like this.bar = function(){...}

? The reasons were speed and memory efficiency.

Okay, now you have a good reason to turn the clock back and sacrifice speed / efficiency to achieve "separable" methods - that is, methods specific to their this

bound-ready constructor instance , which is what you want.

If the methods are called many times, then the inefficiency of the construction will be more than compensated for if not used bind()

when calling the function. Plus, you get the syntactic convenience you're looking for.

Here's a redrawn version of your example to better demonstrate syntactic usability:



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

      

So, you need to do two things:

  • define methods inside constructor
  • bind this

    to methods.
+4


source


I think another way to do this is to take advantage of a closure. Each callback that is used in a promise can contain references to some of the root scope variables in which it was created.

Usually most people do to create a named variable self

that contains a reference to the external one this

. So when you execute a promise function, the closure will maintain the link and therefore be able to access the scope.



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

      

To be honest, I'm not sure which one is more graceful. I'm not sure if there are other ways to do what you want without using this method or method bind

.

+1


source


The best solution in my opinion is to add a method context

to promises to indicate this

for function calls in future calls then

. This is what I seem to remember seeing in some libraries, not remembering where and what is implemented in my own. The idea is to write

promise.context(this).then(myfunc)

      

I don't know Q, so I don't know if it would be easy or even possible to extend / customize it this way. Here's a toy that implements this idea using native JS promises:

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

      

Actually, you can often write your code so that it doesn't get littered with calls context

by doing it once when you create a promise. So:

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

      

This approach saved me from limitless escalation and has zero cons that I see. I don't see a more native way to do this, whatever that means.

Less intrusive approach?

As a more direct answer to your question, you can introduce some sugar - a method that binds and calls in Q.fcall

one fell swoop, like this:

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

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

      

+1


source


If you are using ES6 you can use arrow functions and lexical scope.

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

      

+1


source







All Articles