IDisposable/RAII en TypeScript?
¿Cuál es la forma idiomática de lograr una limpieza determinista en TypeScript? En otras palabras, ¿existe un equivalente del using (IDisposable) de C# o del RAII de C++?
¿O debería simplemente ceñirme a finally?
Contexto: En una aplicación SPA (Aurelia/ASP.NET Core Web API), estoy tratando de dar una indicación al usuario de que los datos se están obteniendo actualmente desde la Web API. Una vez que la obtención se haya completado, me gustaría eliminar la indicación de la interfaz de usuario incluso si se ha lanzado una excepción. Estoy considerando envolver eso en una "clase RAII" para reutilización y sintaxis más limpia (para no tener que llenar el código con finally)...
Respuesta #1
A partir de Typescript 5.2, sí existe un equivalente; declaraciones using.
Para usar, agrega implements Disposable a tu clase:
class MyClass implements Disposable {
[Symbol.dispose]() {
console.log("Disposing!");
}
}
Ahora puedes aprovechar las declaraciones using, su uso es el siguiente:
using myClass = new MyClass;
El método dispose se invocará al final del alcance.
Para más información y ejemplos, consulte: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html
Respuesta #2
No hay una declaración using en Typescript, siempre puedes recurrir a try-finally, que es para lo que using es azúcar sintáctica en C# de todos modos.
Otro enfoque sería crear tu propio using con una función.
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");
})
Si quieres usarlo con promesas, podrías crear una versión asíncrona en la que disposable devuelva una promesa y el parámetro action también devuelva una promesa:
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");
})
})();
