Best practice to hang on to variables when using Promises

javascript, node.js, promise

Solution

Note: I'm using bluebird in this answer.

There are 3 ways to do what you want: closures, binding, and `Promise.using`.

Closure is the way @Sukima showed.

function promisedFunc() {
    var db;
    return getCollection().then(function(col) {
        db = col;
        return db.query(stuff);
    }).then(function() {
        return db.query(otherStuff);
    });
}

Binding: using `Promise.bind`, you can make `this` an object that will hold values.

function promisedFunc() {
    return getCollection().bind({}).then(function(col) {
        this.db = col;
        return this.db.query(stuff);
    }).then(function() {
        return this.db.query(otherStuff);
    });
}

Finally, the last way, introduced by bluebird v2, is using real resource management.

function promisedFunc() {
    return Promise.using(getDb(), function(db) {
        return db.query(stuff).then(function() {
            return db.query(otherStuff);
        });
    });
}

I'm going to explain the `getDb` method further down.

The last way provides another very interesting benefit: disposing resources. For example, you often have to call a `close` method for database resources. `Promise.using` lets you create disposers, running once the promises in it are resolved (or not).

To see why this is an advantage, let's review the 3 ways to do this.

Closure:

var db, close;
return getCollection().spread(function(col, done) {
    db = col;
    close = done;
    return db.query(stuff);
}).then(function() {
    return db.query(otherStuff);
}).finally(function() {
    close();
});

And yes, it means you have to write all this boilerplate every time you use the db connection. No other choice.

Now let's see the binding way:

return getCollection().bind({}).spread(function(col, done) {
    this.db = col;
    this.close = done;
    return this.db.query(stuff);
}).then(function() {
    return this.db.query(otherStuff);
}).finally(function() {
    this.close();
});

Now however, this can be modularized.

var db = {
    getDb: function() { return getCollection().bind({}); },
    close: function() { this.close(); }
};

return db.getDb().then(function() {
    return this.db.query(stuff);
}).then(function() {
    return this.db.query(otherStuff);
}).finally(db.close);

This is already a lot nicer! But we still have to think about using `finally`.

And then, the way introduced by bluebird, `Promise.using`. By declaring it this way:

function getDb() {
    var close;
    return getCollection().spread(function(db, done) {
        close = done;
        return db;
    }).disposer(function() {
        close();
    });
}

You can simply use it as seen before:

return Promise.using(getDb(), function(db) {
    return db.query(stuff).then(function() {
        return db.query(otherStuff);
    });
});

No need to think about `finally`, and no boilerplate.

Problem

I am new to Promises and I was wondering what the best practice is to keep variables while going down the chain? Connecting to MongoDB via Promise is pretty straight forward: ``` connectToMongoDB(data).done(function(db) { var collection = db.collection('test_inserts'); // do more stuff here }); ``` But what happens if I have to connect to two different databases? ``` connectToMongoDB1(data1).then(function(db1) { return connectToMongoDB2(data2); }).done(function(db2) { var collection = db1.collection('test_inserts'); // ERROR: db1 is undefined }); ``` That error makes perfect sense. But how do I forward `db1` without altering my `connectToMongoDB2()` function, because I want to keep `connectToMongoDB2()` and all my promises in general very generic? I mean, I could wrap an object around that stores all the relevant stuff, but that looks kind of hacky: ``` var relevantStuff = {}; connectToMongoDB1(data1).then(function(db1) { relevantStuff.db1 = db1; return connectToMongoDB2(data2); }).done(function(db2) { var collection = relevantStuff.db1.collection('test_inserts'); // do more stuff here }); ``` What is the best practice?

Original source

Related problems