链式承诺并保留“这个”

时间:2014-01-23 17:53:14

标签: javascript ember.js promise

我有一个点击处理程序,需要一个接一个地进行多个异步调用。我选择使用promises(RSVP来构建这些调用,确切地说)。

下面,您可以在控制器内看到clickA处理程序(它是一个Ember应用程序,但我认为问题更为笼统):

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

它看起来很棒,但现在我必须添加第二个重复使用某些步骤的操作。

由于每个内部函数都需要从控制器访问属性,因此一种解决方案是使它们成为控制器的方法:

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

所以,我的问题是:有更好的方法吗?我可以以某种方式摆脱所有jQuery.proxy()次呼叫吗?

3 个答案:

答案 0 :(得分:4)

解决方案是使用更好的诺言库。

Bluebird有一个bind函数,可让您将上下文绑定到整个承诺链(您传递给thencatchfinally的所有函数用这个上下文调用。)

这是一篇关于使用绑定承诺的文章(我写的),就像你想保留一个控制器/资源一样:Using bound promises to ease database querying in node.js

我实现了这样的承诺:

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

允许我这样做:

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 

答案 1 :(得分:3)

我可能会遗漏一些东西,但这会解决你的问题吗?

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

只需将actions包装在IIFE中,并将常用功能存储在那里,只显示您需要的最终功能。但我根本不认识Ember,也许我错过了一些基本的东西......

答案 2 :(得分:2)

浏览器对所有函数都有“绑定”方法。为Function#bind创建一个pollyfill也很容易。

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

jQuery.proxy方法基本上做同样的事情。