Question

My question is about the elegant parallelization of promises in BlueBird when you need to pass both the context and argument to the functions building the promises.

To make my problem understandable and testable, I made an example with no dependency.

Let's suppose I do computation ( 1/(xxx) + 1/(x*x) ) involving an asynchronous "computer" (whose resources must be released). The square and the cube are computed asynchronously and independently.

I can do my computation like this :

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

But I find this usage of all too heavy compared to what is theoretically possible. When you pass a function as argument to then, it's executed. When you pass functions to all, they're not, there's an error. I suspect I'm missing a utility or pattern...

Is there a solution to change it to something simpler in this style :

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

?

I could probably hack Promise.prototype.all or define a new function to avoid increasing the polymorphism but I'm only interested in solutions not involving the modification of objects I don't own.


Annex:

Here's how is defined the "computer" for my test :

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 }
Was it helpful?

Solution

You don't have to use Promise.all there. Instead of doing:

.then(function(arg){
    return Promise.all([
        proto.square(arg),
        proto.cube(arg)
    ]);
}).spread(...

You can simply use:

.then(function(arg){
    return [proto.square(arg), proto.cube(arg)];
}).spread(...

If we had arrow functions in node.js, it'd be as simple as:

.then(arg => [proto.square(arg), proto.cube(arg)]).spread(...

Promise.all is to be used when you need to start a chain of promises with at least 2 promises. For example:

var promise1 = somePromise();
var promise2 = somePromise2();

// Start the chain here
Promise.all([promise1, promise2])
.spread(function(value1, value2) {
    // ...
});

OTHER TIPS

For management of resources use case like you are mentioning, bluebird has Promise.using(). Promise.using() lets you setup disposer() function for automatically closing the asynchronously-retrieved resource when you done using

Promise.join() would help as well to combine the results of the cube and square async methods

Here I slightly re-wrote your InitComputer example to illustrate how this works - it now returns the Computer instance with val added as a property, instead of the val, and I placed endComputer on the proto also

note: you can always use Promise.method() like so instead of returning a deferred:

var invert = Promise.method(function invert(v){ return 1/v })

new initComputer:

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

new code:

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);
    });
})
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top