问题
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