Skip to content

Instantly share code, notes, and snippets.

@uniphil
Created October 26, 2015 04:17
Show Gist options
  • Save uniphil/83ccc03e3161f53a7508 to your computer and use it in GitHub Desktop.
Save uniphil/83ccc03e3161f53a7508 to your computer and use it in GitHub Desktop.
thenAlways: safer resource management with promises
import assert from 'assert';
import { MongoClient as mongoClient } from 'mongodb';
import thenAlways from './then-always';
/**
* Use thenAlways to make a helper to safely initialize, use, and clean up a mongodb connection
* @param {string} dbURI The mongodb test database to connect to
* @param {func} doStuff A callback to run some tasks with the test database
* @returns {Promise} A promise resolving or rejecting when everything's done
*/
const testWithDB = (dbURI, doStuff) => () => // wrap in an extra fn so we can declare tests without running them
thenAlways(mongoClient.connect(dbURI), dbConn => dbConn.close(), dbConn => {
const dropTestDB = () => dbConn.command({dropDatabase: 1});
return thenAlways(dropTestDB(), dropTestDB, () => doStuff(dbConn)); // clean out the test db before and after
});
// Set up some tests!
const TEST_DB_URI = 'mongodb://localhost:27017/demo-testing-temp-database';
const testInsertsWithoutErr = testWithDB(TEST_DB_URI, db =>
db.collection('testCollection').insert({a: 1}));
const testCanFindDoc = testWithDB(TEST_DB_URI, db => {
const collection = db.collection('testCollection');
return collection.insert({a: 1})
.then(() => collection.find({}).toArray())
.then(objs => {
assert.equal(objs.length, 1, 'Expected to find one document in the collection');
assert.equal(objs[0].a, 1, 'Expected document.a to be 1');
assert.ok(objs[0]._id, 'Expected document to have an _id');
});
});
const testInsertAndFindMany = testWithDB(TEST_DB_URI, db => {
const collection = db.collection('testCollection');
return collection.insertMany([{a: 1}, {a: 1, b: 2}])
.then(() => Promise.all([
collection.find({a: 1}).toArray(),
collection.find({b: 2}).toArray()
]))
.then(results => {
assert.equal(results[0].length, 2, 'Expected to find 2 {a: 1}');
assert.equal(results[1].length, 1, 'Expected to find 1 {b: 2}');
});
});
// Run the tests!
Promise.resolve(console.log('Starting tests...')) // .resolve only so we can start test1 with a .then
.then(testInsertsWithoutErr)
.then(testCanFindDoc)
.then(testInsertAndFindMany)
.catch(err => {
console.error(`Tests failed: ${err}`)
return Promise.reject(err); // re-reject after logging the error
})
.then(() => console.log('All tests finished successfully!'));
/**
* Safely initialize, use, and clean up a resource
*
* @param {Promise} initPromise A promise for any kind of resource initialization
* @param {func} cleanupFn A callback to clean up the resource, returning a promise if async
* @param {func} doStuff Some things to do with the resource, returning a promise if async
* @returns {Promise} A promise that resolves or rejects after the resource has been initialized,
* the doStuff is finished, and cleanupFn is over.
*/
const thenAlways = (initPromise, cleanupFn, doStuff) =>
initPromise.then(initValue =>
Promise.resolve(doStuff(initValue)) // cast doStuff's return to a promise
.catch(stuffErr => { // doStuff failed
cleanupFn(initValue); // clean up regardless
return Promise.reject(stuffErr); // re-reject doStuff's error
})
.then(() => cleanupFn(initValue))); // clean up if doStuff succeeded
// no .catch: if resource init fails, don't doStuff or cleanUp, and let the caller handle it.
export default thenAlways;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment