Chained promises and preserving `this`

心已入冬 提交于 2020-02-02 06:05:47

问题


I have a click handler that needs to make several async calls, one after another. I've chosen to structure these calls using promises (RSVP, to be precise).

Below, you can see the clickA handler, inside the controller (it's an Ember app, but the problem is more general, I think):

App.SomeController = Ember.Controller.extend({

  actions: {

    clickA: function() {
      var self = this;

      function startProcess() {
        return makeAjaxCall(url, {
          'foo': self.get('foo')
        });
      }

      function continueProcess(response) {
        return makeAjaxCall(url, {
          'bar': self.get('bar')
        });
      }

      function finishProcess(response) {
        return new Ember.RSVP.Promise(...);
      }

      ...

      startProcess()
        .then(continueProcess)
        .then(finishProcess)
        .catch(errorHandler);
    }
  }
});

It looks great, but now I have to add a second action that reuses some of the steps.

Since each of the inner functions needs to access properties from the controller, one solution would be to make them methods of the controller:

App.SomeController = Ember.Controller.extend({

  startProcess: function() {
    return makeAjaxCall(url, {
      'foo': this.get('foo')
    });
  },

  continueProcess: function(response) {
    return makeAjaxCall(url, {
      'bar': this.get('bar')
    });
  },

  finishProcess: function(response) {
    return new Ember.RSVP.Promise(...);
  },

  actions: {
    clickA: function() {
      this.startProcess()
        .then(jQuery.proxy(this, 'continueProcess'))
        .then(jQuery.proxy(this, 'finishProcess'))
        .catch(jQuery.proxy(this, 'errorHandler'));
    },

    clickB: function() {
      this.startProcess()
        .then(jQuery.proxy(this, 'doSomethingElse'))
        .catch(jQuery.proxy(this, 'errorHandler'));
    }
  }
});

So, my question is: is there a better way? Can I get rid of all those jQuery.proxy() calls somehow?


回答1:


A solution would be to use a better promise library.

Bluebird has a bind function which lets you bind a context to the whole promise chain (all functions you pass to then or catch or finally are called with this context ).

Here's an article (that I wrote) about bound promises used like you want to keep a controller/resource : Using bound promises to ease database querying in node.js

I build my promise like this :

// returns a promise bound to a connection, available to issue queries
//  The connection must be released using off
exports.on = function(val){
    var con = new Con(), resolver = Promise.defer();
    pool.connect(function(err, client, done){
        if (err) {
            resolver.reject(err);
        } else {
            // the instance of Con embeds the connection
            //  and the releasing function
            con.client = client;
            con.done = done;
            // val is passed as value in the resolution so that it's available
            //  in the next step of the promise chain
            resolver.resolve(val);
        }
    });
    // the promise is bound to the Con instance and returned
    return resolver.promise.bind(con);
}

which allows me to do this :

db.on(userId)          // get a connection from the pool
.then(db.getUser)      // use it to issue an asynchronous query
.then(function(user){  // then, with the result of the query
    ui.showUser(user); // do something
}).finally(db.off);    // and return the connection to the pool 



回答2:


I may be missing something, but would this solve your issue?

actions: (function() {
    var self = this;
    function startProcess() { /* ... */ }
    function continueProcess(response) { /* ... */ }
    function finishProcess(response) { /* ... */ }
    function doSomethingElse(response) { /* ... */ }
    /*  ... */
    return {
        clickA: function() {
            startProcess()
                .then(continueProcess)
                .then(finishProcess)
                .catch(errorHandler);
        },
        clickB: function() {
            startProcess()
                .then(doSomethingElse)
                .catch(errorHandler));      
        }
    };
}());

Just wrap the actions in an IIFE, and store the common functions there, exposing only the final functions you need. But I don't know Ember at all, and maybe I'm missing something fundamental...




回答3:


Browsers have a "bind" method on all functions. It's also easy to create a pollyfill for Function#bind.

this.startProcess()
    .then(this.continueProcess.bind(this))
    .then(this.finishProcess.bind(this))
    .catch(this.errorHandler.bind(this));

The jQuery.proxy method essentially does the same thing.



来源:https://stackoverflow.com/questions/21315817/chained-promises-and-preserving-this

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!