IDisposable/RAII i TypeScript?


Vad är det idiomatiska sättet att uppnå deterministisk städning i TypeScript? Med andra ord, finns det en motsvarighet till C#'s using (IDisposable) eller C++'s RAII?

Eller borde jag bara hålla mig till finally?

Kontext: I en SPA-applikation (Aurelia/ASP.NET Core Web API) försöker jag ge användaren en indikation på att data för närvarande hämtas från Web API. När hämtningen är klar vill jag ta bort UI-indikationen även om ett undantag har kastats. Jag tittar på att slå in det i en "RAII-klass" för återanvändning och renare syntax (så att jag inte behöver strö finally överallt i koden)...

Svar #1

Från och med Typescript 5.2 finns det ja en motsvarighet; using-deklarationer.

För att använda, lägg till implements Disposable till din klass:

    class MyClass implements Disposable {
        [Symbol.dispose]() {
            console.log("Disposing!");
        }
    }

Du kan nu dra nytta av using-satser, användning enligt följande:

using myClass = new MyClass;

Dispose-metoden kommer att anropas i slutet av omfattningen.

För mer information och exempel, se: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html

Svar #2

Det finns inget using-uttalande i Typescript, du kan alltid falla tillbaka på try-finally vilket är vad using egentligen är syntaktiskt socker för i C#.

Ett annat tillvägagångssätt skulle vara att skapa ditt eget using med en funktion.

interface IDisposable {
    dispose();
}

function using<T extends IDisposable,
    T2 extends IDisposable,
    T3 extends IDisposable>(disposable: [T, T2, T3], action: (r: T, r2: T2, r3: T3) => void);
function using<T extends IDisposable, T2 extends IDisposable>(disposable: [T, T2], action: (r: T, r2: T2) => void);
function using<T extends IDisposable>(disposable: T, action: (r: T) => void);
function using(disposable: IDisposable[], action: (...r: IDisposable[]) => void)
function using(disposable: IDisposable | IDisposable[], action: (...r: IDisposable[]) => void) {
    let disposableArray = disposable instanceof Array ? disposable : [disposable];
    try {
        action(...disposableArray);
    } finally {
        disposableArray.forEach(d => d.dispose());
    }
}


// Usage
class UserNotify { dispose() { console.log("Done"); } }

class Other { dispose() { console.log("Done Other"); } }

using(new UserNotify(), userNotify => {
    console.log("DoStuff");
})
// It will type the arrow function parameter correctly for up to 3 parameters, but you can add more overloads above.
using([new UserNotify(), new Other()], (userNotify, other) => {
    console.log("DoStuff");
})

Om du vill använda det med promises kan du skapa en asynkron version där disposable returnerar ett promise och action-parametern också returnerar ett promise:

interface IDisposableAsync {
    dispose(): Promise<void> | void;
}
function usingAsync<T extends IDisposableAsync, T2 extends IDisposableAsync, T3 extends IDisposableAsync>(disposable: [T, T2, T3], action: (r: T, r2: T2, r3: T3) => Promise<void>): Promise<void>;
function usingAsync<T extends IDisposableAsync, T2 extends IDisposableAsync>(disposable: [T, T2], action: (r: T, r2: T2) => Promise<void>): Promise<void>;
function usingAsync<T extends IDisposableAsync>(disposable: T, action: (r: T) => Promise<void>): Promise<void>;
function usingAsync(disposable: IDisposableAsync[], action: (...r: IDisposableAsync[]) => Promise<void>): Promise<void>
async function usingAsync(disposable: IDisposableAsync | IDisposableAsync[], action: (...r: IDisposableAsync[]) => Promise<void>): Promise<void> {
    let disposableArray = disposable instanceof Array ? disposable : [disposable];
    try {
        await action(...disposableArray);
    } finally {
        for (let d of disposableArray) {
            let result = d.dispose();
            if (result !== null) {
                await result;
            }
        }
    }
}

// Usage
class UserNotify { dispose() { console.log("Done"); } }
class Other { dispose() { console.log("Done Other"); } }
function delay() {
    return new Promise((r)=> setTimeout(() => {
        r();
    }, 100));
}
(async function () {
    await usingAsync(new UserNotify(), async userNotify => {
        await delay()
        console.log("DoStuff");
    })

    await usingAsync([new UserNotify(), new Other()], async (userNotify, other) => {
        await delay()
        console.log("DoStuff");
    })
})();

Relaterade artiklar