Les promesses enchaînées et la préservation de «cela»

J'ai un gestionnaire de clics qui doit effectuer plusieurs appels asynchrones, l'un après l'autre. J'ai choisi de structurer ces appels en utilisant des promesses ( RSVP , pour être précis).

Ci-dessous, vous pouvez voir le gestionnaire clickA , à l'intérieur du contrôleur (c'est une application Ember, mais le problème est plus général, je pense):

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

Il semble génial, mais maintenant je dois ajouter une seconde action qui réutilise certaines des étapes.

Étant donné que chacune des fonctions internes doit accéder aux propriétés du contrôleur, une solution serait de les transformer en méthodes du contrôleur:

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

Donc, ma question est la suivante: existe-t-il une meilleure façon? Puis-je me débarrasser de tous ces jQuery.proxy() manière ou d'une autre?

Une solution serait d'utiliser une meilleure bibliothèque prometteuse.

Bluebird a une fonction de liaison qui vous permet de lier un contexte à la chaîne de promesse entière (toutes les fonctions que vous passez ou catch ou finally sont appelées avec ce contexte).

Voici un article (que j'ai écrit) sur les promesses liées que vous souhaitez garder un contrôleur / ressource: en utilisant des promesses liées pour faciliter l'interrogation de la base de données dans node.js

Je crée ma promesse comme ceci:

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

Ce qui me permet de faire ceci:

 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 

Je risque peut-être manquer quelque chose, mais cela résoudrait-il votre problème?

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

Il suffit d'envelopper les actions dans un IIFE, et de stocker les fonctions communes là-bas, en exposant uniquement les fonctions finales dont vous avez besoin. Mais je ne connais pas Ember du tout, et peut-être me manque-t-il quelque chose de fondamental …

Les navigateurs ont une méthode "bind" sur toutes les fonctions. Il est également facile de créer un pollyfill pour Function # bind.

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

La méthode jQuery.proxy essentiellement la même chose.