Bringing Python's with
to JS/TS.
Python's with
syntax provides a concise, idiomatic, repeatable method of automatically performing setup and cleanup for an
operation such as reading a file from disk, making a network request, or interacting with a database. In addition, it provides
a single logical method of handling any errors that may arise.
To get started, create a context guard object with an enter
method and (optionally) an exit
method. The enter
method
should perform any required setup and return the value you'll be using:
import { using } from '@prgma/using';
const FileGuard = {
enter() {
// get handle
const handle = fs.open('somefile');
return handle;
},
exit(handle) {
// close handle
fs.close(handle);
},
};
using(FileGuard, handle => {
// do something with prepared handle
console.log(handle);
});
If you prefer a more functional approach, you can instead provide a function that returns the value to use and (optionally) a cleanup function. If you're returning both, make sure to wrap them in an array:
import { using } from '@prgma/using';
function open(path: string) {
return function() {
// get handle
const handle = fs.open(path);
return [
handle,
handle => {
// close handle
fs.close(handle);
},
];
};
}
using(open('foo.txt'), handle => {
// do something with prepared handle
console.log(handle);
});
Setup and cleanup processes generally require some form of interaction with external services, which is often made simpler
by using an async
function. Object context guards may have enter
return a Promise that resolves to the value to use, which
will work as you would expect:
import { using } from '@prgma/using';
async function getApiMessageForUser(userId: number): Promise<string> {
return `Hello, user ${userId}!`;
}
function getMessage(userId: number): ContextGuards<string> {
return {
async enter() {
const message = await getApiMessageForUser(userId);
return message;
},
exit() {
// cleanup
},
};
}
using(getMessage(1), message => {
// do something with fetched message
console.log(message);
});
exit
may also be async
- if so, you can await
the entire using
call, which will resolve when the cleanup has finished.
(async () => {
await using(withAsyncCleanup, () => {/* ... */});
console.log('cleanup finished!');
})();
The included open
guard works just like Python's open
- provide a file path and access flags ('r'
by default) to get a
file descriptor ready to use and automatically cleaned up upon completion:
import { using } from '@prgma/using';
import { open } from '@prgma/using/fs';
import * as fs from 'fs';
using(open('foo.txt', 'rw'), fd => {
fs.write(fd, 'Hello, world!');
});