Construcción fluida de promesas paralelas
-
21-12-2019 - |
Pregunta
Mi pregunta es sobre la elegante paralelización de promesas en Azulejo cuando necesita pasar tanto el contexto como el argumento a las funciones que crean las promesas.
Para que mi problema sea comprensible y comprobable, hice un ejemplo sin dependencia.
Supongamos que hago cálculos ( 1/(xXx) + 1/(x*x) ) que involucra una "computadora" asincrónica (cuyos recursos deben liberarse).El cuadrado y el cubo se calculan de forma asincrónica e independiente.
Puedo hacer mi cálculo así:
InitComputer(2) // returns a promise
.then(invert)
.then(function(arg){
return Promise.all([
proto.square(arg),
proto.cube(arg)
]);
}).spread(function(sq, cu){
this.set(sq + cu);
}).catch(function(err){
console.log('err:', err);
}).finally(endComputer);
Pero encuentro este uso de all
demasiado pesado en comparación con lo que es teóricamente posible.Cuando pasas una función como argumento a then
, se ejecuta.Cuando pasas funciones a all
, no lo son, hay un error.Sospecho que me falta una utilidad o patrón...
¿Existe una solución para cambiarlo a algo más simple en este estilo?
InitComputer(2)
.then(invert)
.all([
proto.square,
proto.cube
]).spread(function(sq, cu){
this.set(sq + cu);
}).catch(function(err){
console.log('err:', err);
}).finally(endComputer);
?
Probablemente podría hackear Promesa.prototipo.todo o definir una nueva función para evitar aumentar el polimorfismo, pero solo me interesan soluciones que no impliquen la modificación de objetos que no me pertenecen.
Anexo:
Así es como se define la "computadora" para mi prueba:
var Promise = require("bluebird");
function Computer(){}
function InitComputer(v){
// initializing a computer is asynchronous and may fail hence the promise
var c = new Computer(), resolver = Promise.defer();
setTimeout(function(){
if (v>1) resolver.resolve(v);
else resolver.reject(new Error("bad value: "+v));
},100);
return resolver.promise.bind(c);
}
var proto = Computer.prototype;
proto.square = function(x){
// imagine this really uses the computer and is asynchronous
if (!this instanceof Computer) throw new Error('not a computer');
return x*x
}
proto.cube = function(x){ return x*x*x }
proto.set = function(v){ this.value = v }
function endComputer(){
// releases resources here
console.log('value:', this.value);
}
// this asynchronous function doesn't involve or know the computer
function invert(v){ return 1/v }
Solución
No tienes que usar Promise.all
allá.En lugar de hacer:
.then(function(arg){
return Promise.all([
proto.square(arg),
proto.cube(arg)
]);
}).spread(...
Simplemente puedes usar:
.then(function(arg){
return [proto.square(arg), proto.cube(arg)];
}).spread(...
Si tuviéramos funciones de flecha en node.js, sería tan simple como:
.then(arg => [proto.square(arg), proto.cube(arg)]).spread(...
Promise.all
debe usarse cuando sea necesario comenzar una cadena de promesas con al menos 2 promesas.Por ejemplo:
var promise1 = somePromise();
var promise2 = somePromise2();
// Start the chain here
Promise.all([promise1, promise2])
.spread(function(value1, value2) {
// ...
});
Otros consejos
Para el caso de uso de gestión de recursos como el que usted menciona, bluebird tiene Promise.using()
. Promise.using()
te permite configurar disposer()
función para cerrar automáticamente el recurso recuperado de forma asincrónica cuando termine de usar
Promise.join()
También ayudaría a combinar los resultados de la cube
y square
métodos asíncronos
Aquí reescribí ligeramente tu InitComputer
ejemplo para ilustrar cómo funciona esto: ahora devuelve el Computer
instancia con val agregado como propiedad, en lugar de val, y coloqué endComputer
en el proto también
nota:siempre puedes usar Promise.method()
así en lugar de devolver un diferido:
var invert = Promise.method(function invert(v){ return 1/v })
nueva computadora de inicio:
function InitComputer(v){
var c = new Computer(), resolver = Promise.defer();
setTimeout(function(){
if (v>1) {
c.val = v;
resolver.resolve(c);
}
else resolver.reject(new Error("bad value: "+v));
},100); /** notice resource disposer function added below **/
return resolver.promise.bind(c).disposer(function(compu){compu.endComputer()});
}
nuevo código:
Promise.using(InitComputer(1.2), function(computer){
return invert(computer.val)
.then(function(inverted){
return Promise.join(computer.square(inverted), computer.cube(inverted),
function(sq, cu){
computer.set(sq + cu)
}
)
})
.catch(function(err){
console.log('err:', err);
});
})