Skip to content

Instantly share code, notes, and snippets.

@spion
Last active June 21, 2019 17:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save spion/ed8deb7a3b4add0a6d727dc78fe635d8 to your computer and use it in GitHub Desktop.
Save spion/ed8deb7a3b4add0a6d727dc78fe635d8 to your computer and use it in GitHub Desktop.
/**
* Allocate a callback based resource, safely
*
* Example:
* function connectionResource(url) {
* return { acquire: async () => pg.connect(url), dispose: async conn => conn.dispose() }
* }
* usingPromise(connectionResource(process.env.DATABASE_URL), async conn => {
* await conn.query(...);
* do other things
* return some result;
* });
*/
function usingCallback(resource, fn) {
return new Promise((resolve, reject) => {
// Make sure any synchronous throws from the callback based library crash the process, but if we got the resource,
// continue in "crash-safe" zone.
// Additionally an "operational" error during acquiring is assumed not to need disposal; depending on how paranoid you are,
// or how much you trust the library author, you might want to crash the process there too.
process.nextTick(() => {
resource.acquire((err, res) => { if (err) reject(err); else resolve(res); });
});
}).then(item => {
return Promise.resolve(item)
.then(fn) // any exceptions or errors of fn are propagated (crash-safe)
.finally(() => { // runs regardless of errors
return new Promise(resolve => {
// make sure that any failure to dispose crashes the process.
process.nextTick(() => {
resource.dispose(item, err => { if (err) throw err; else resolve(); });
});
});
});
});
}
/**
* "Termination" callback for promise errors
*/
function terminate(e) {
process.nextTick(() => { throw e });
throw e;
}
/**
* Allocate a resource safely (promise version)
*
* Example:
* function connectionResource(url) {
* return {acquire: cb => pg.connect(url), dispose: (conn, cb) => conn.dispose()}
* }
* usingCallback(connectionResource(process.env.DATABASE_URL), async conn => {
* await conn.query(...);
* do other things
* return some result;
* });
*/
function usingPromise(resource, fn) {
return Promise.resolve()
.then(() => resource.acquire())
// bail if acquiring fails
.catch(terminate)
.then(item =>
Promise.resolve(item).then(fn)
// bail if disposing fails
.finally(() => resource.dispose(item).catch(terminate)));
}
/**
* Call async functions that may leave something in an unsafe state, safely
*
* @example
* unsafeCall(() => myObject.mutateStateUnsafelyReturningPromise(x))
*/
function unsafeCall(fn) {
return Promise.resolve().then(fn).catch(terminate);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment