Last active
June 21, 2019 17:53
-
-
Save spion/ed8deb7a3b4add0a6d727dc78fe635d8 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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