4

I'm trying to use MongoDB with Promises in Node 4.x

In this example I want to:

  1. Connect to my mongodb
  2. THEN delete everything with a given Key
  3. THEN insert one record
  4. THEN close the connection

luckily the mongodb client spits out promises when you don't give it a callback. Here's what I came up with.

const MongoClient = require('mongodb').MongoClient;
const test = require('assert');

function insertDoc(doc, collName) {
  return MongoClient.connect('mongodb://localhost:27017/myDB')
    .then(db => {
      const col = db.collection(collName);
      return col.deleteMany({ 'Key': doc.key })
        .then(() => col.insertOne(doc))
        .then(result => test.equal(1, result.insertedCount))
        .then(() => db.close);
    });
}

The code seems to work but the nested .then() "feels" wrong. Any ideas how to do it so that the db object can be used when I'm ready to .close() it?

Raychaser
  • 340
  • 2
  • 12
  • 1
    There's nothing wrong with your code and nested `then`s (except that you should use `finally` not `then` for the `close` callback) – Bergi Jul 27 '16 at 22:09
  • @Bergi There's no `.finally` in es6 promises is there? – jib Jul 27 '16 at 22:57
  • @jib: [There's not](http://stackoverflow.com/a/32362233/1048572), but you still need its functionality for `close` – Bergi Jul 27 '16 at 23:29
  • 1
    @Bergi `.then(() => db.close(), e => (db.close(), Promise.reject(e)))` or something like it, agree. – jib Jul 28 '16 at 00:39

3 Answers3

2

One option is to treat the promises more as values, then pull out the wrapped value when you need it. It has its own readability downsides though.

e.g.

function insertDoc(doc, collName) {
  const db = MongoClient.connect('mongodb://localhost:27017/myDB');
  const col = db.then(db => db.collection(collName));

  return col.deleteMany({ 'Key': doc.key })
    .then(() => col.insertOne(doc))
    .then(result => test.equal(1, result.insertedCount))
    // You've still got the 'db' promise here, so you can get its value
    // to close it.
    .then(() => db.then(db => db.close()));
}
loganfsmyth
  • 156,129
  • 30
  • 331
  • 251
  • 1
    On readability, I sometimes use this, but find calling it `haveDb` to be beneficial, to distinguish it from its value. e.g. `haveDb.then(db => {})`. As in "when I have db, then do this with it". – jib Jul 27 '16 at 20:49
  • Nice. I like this slightly better than what I was doing – Raychaser Jul 27 '16 at 21:06
1

As it stands you can use a variable in the outer scope to achieve this:

let db;

function insertDoc(doc, collName) {
  return MongoClient.connect(dsn)
    .then(connectedDb => {
      db = connectedDb;
      return col.deleteMany(doc)
    }) // now you can chain `.then` and still use `db`
}

There are a few possible alternatives such as passing db along, but this would seem weird to me. If you want to keep this flow but still take advantage of asynchronicity, you can use async/await. Right now you will need a transpiler such as babel and something like the regenerator-runtime to use it.

async function insertDoc(doc, collName) {
  const db = await MongoClient.connect(dsn);
  const col = db.collection(collName);
  await col.deleteMany({Key: doc.key});
  const result = await col.insertOne(doc);
  await test.equal(1, result.insertedCount) // is this asynchronous?
  return db.close();
}

You can also use co/yield to avoid transpiling although it's a bit more verbose.

Explosion Pills
  • 188,624
  • 52
  • 326
  • 405
  • 1
    I sometimes do the first one, but it's not very clean scoping-wise. It can have side effects like `db` sticking around after it should have been garbage collected. – jib Jul 27 '16 at 20:43
0

I find what you have to be the most readable pattern of alternatives I've seen. I myself use indentation (nested .then's) to access previous values (and that's the only time I do it!)

So many things end up affecting how the code looks and reads. Take your col temporary for example. If it weren't needed, your code might have looked like this:

var insertDoc = (doc, collName) => MongoClient.connect('mongodb://localhost:x/DB')
  .then(db => db.collection(collName).deleteMany({ 'Key': doc.key })
    .then(() => db.collection(collName).insertOne(doc))
    .then(result => test.equal(1, result.insertedCount))
    .then(() => db.close))
  .then(() => doSomethingElse());

Note the extra ) after db.close). An editor that bracket-matches is helpful. :)

I'm not suggesting dropping col for the sake of code beauty. I'm only showing this because I think it highlights how indentation shows the scope of the db value well. It's when I saw code like this, that I started liking this pattern.

In real life code doesn't always collapse neatly, but I like a pattern where it does when it can.

jib
  • 40,579
  • 17
  • 100
  • 158