I have below function from a 3rd party package which I can't modify
async function runTransaction(callback) {
const client = await createClient();
try {
await client.query("BEGIN");
await callback(client);
} finally {
await client.query("COMMIT");
}
}
Normally I have to create an async function and pass it to the runTransaction
async function update(client) {
await client.query("UPDATE ...")
}
await runTransaction(update);
I wonder if there is a way to use runTransaction in an async generator pattern. Here is my sketch. The end result I want is BEGIN then UPDATE then COMMIT
async *clientGenerator() {
await runTransaction(async client => {
yield client
});
// this will not work as yield can't be used in a callback
}
async function main() {
const dbClientGenerator = await clientGenerator();
const dbClientHolder = await dbClientGenerator.next();
await dbClientHolder.value.query("UPDATE ...");
}
I also tried below, but the callback gets called at the same time as await client.query("COMMIT"); in runTransaction.
async *clientGenerator() {
yield new Promise(resolve => {
runTransaction(async client => {
resolve(sql);
})
})
}
Any idea how to achieve this?
You could write1
and use it as
but this is no better (and much more confusing) than the current
Really there is nothing wrong with this. It is known as the promise disposer pattern and guarantees proper cleanup of resources after you're done. Your
mainfunction that manually advances the async iterator is a prime example of what's wrong with this approach: it fails to do error handling and would never commit the transaction. The promise disposer pattern prevents such abuse.The pattern may get superseded by disposables from the explicit resource management proposal which will allow writing
1: or even wrap the
runTransactionyou're given into an equivalent of this, instead of rewriting it, but this is complicated and I wanted to make the point clear.If you absolutely have to do this, here's how:
This handles all kinds of possible failure cases in
runTransactionand in thecallbackiteration, but relies onrunTransactioncalling the callback exactly once (unless there's an error increateClient).