Now that async generators are available in the platforms I care about, I figured I'd try porting Python's ingenious with construct to JS as a utility. My first stab looks like this:

type Index = string | symbol | number;
function isIndex(obj: any): obj is Index {
    return (
        typeof obj === 'string' ||
        typeof obj === 'symbol' ||
        typeof obj === 'number'
type Disposable<TDisposerKey extends Index> = {
    [k in TDisposerKey]: DisposerMethod
type DisposerMethod = () => void | Promise<void>;
type DisposerCallback<TDisposable> = (res: TDisposable) => void | Promise<void>;

function using<
    TDisposerKey extends Index,
    TDisposable extends Disposable<TDisposerKey>
>(resource: TDisposable, key: TDisposerKey): AsyncIterable<TDisposable>;
function using<TDisposable>(
    resource: TDisposable,
    disposer: DisposerCallback<TDisposable>
): AsyncIterable<TDisposable>;
async function* using<
    TDisposerKey extends Index,
    TDisposable extends Disposable<TDisposerKey>
    resource: TDisposable,
    keyOrDisposer: TDisposerKey | DisposerCallback<TDisposable>
): AsyncIterable<TDisposable> {
    try {
        yield resource;
    } finally {
        if (typeof keyOrDisposer === 'function') {
            await keyOrDisposer(resource);
        } else if (isIndex(keyOrDisposer)) {
            await resource[keyOrDisposer]();

async function main(): Promise<any> {
    for await (const db of using(new PouchDB('zz-data\\foo'), 'close')) {
        console.dir(await db.info());
        return await db.get('qwertyuiop');

Does anything jump out at anybody? A for..await..of loop with only one iteration is odd, but it's the only way to handle this lexically, and it saves me having to juggle types for a callback-based alternative which was fiddly to make generic. The overloads are also a mouthful, but that's TS for you.

  • \$\begingroup\$ I can't decide if this is amazing or horrible... the iteration is clever, but quite confusing without knowing exactly how using works. \$\endgroup\$ – Gerrit0 Feb 3 at 19:03
  • \$\begingroup\$ It helps to know the link between iterators and coroutines and how the Python original works. \$\endgroup\$ – millimoose Feb 4 at 19:00
  • \$\begingroup\$ Sadly this messes with type checking because TS doesn’t know that the loop will run (at least) once, so it insists on having undefined be in the return type. \$\endgroup\$ – millimoose Feb 4 at 19:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.