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?
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.
Typically if your managing multiple return values (in your case) it is encapsulated in some form. Here are some popular options:
function promisedFunc() {
var db;
return getCollection()
.then(function(db1) {
db = db1;
// Do stuff
return stuff;
})
.then(function(db1) {
// Do stuff
return db.stuff;
});
}
function getConnection() {
return getCollection()
.then(function(db1) {
// Do stuff
return [stuff, db1];
})
.then(function(args) {
var stuff = args[0];
var db = args[1];
// Do stuff
return [db.moreStuff, db];
});
}
function getConnection() {
function DB(db1, stuff) {
if (db1 instanceof DB) { return new DB(db1.db1, stuff); }
this.db1 = db1;
this.stuff = stuff;
}
// Add to prototype etc.
return getCollection()
.then(function(db1) {
// Do stuff
return new DB(db1, stuff);
})
.then(function(db) {
// Do stuff
return new DB(db, stuff);
});
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With