14 / 37
Что такое using и Disposable в TypeScript 5.2?
Полный ответ
Проблема: ресурсы (файлы, соединения, транзакции) нужно закрывать вручную, и это легко забыть:
TypeScript
// ❌ Без using — нужен try/finally
function readConfig() {
const file = openFile("config.json");
try {
return file.read();
} finally {
file.close(); // легко забыть
}
}
С using очистка происходит автоматически:
TypeScript
// Объект должен реализовать Disposable
class FileHandle implements Disposable {
private handle: number;
constructor(path: string) {
this.handle = openRawFile(path);
console.log(`Opened: ${path}`);
}
read(): string {
return readFromHandle(this.handle);
}
[Symbol.dispose](): void {
closeRawFile(this.handle);
console.log("File closed");
}
}
function readConfig() {
using file = new FileHandle("config.json");
return file.read();
// [Symbol.dispose]() вызывается автоматически при выходе из блока
}
Асинхронный вариант для сетевых ресурсов:
TypeScript
class DbConnection implements AsyncDisposable {
private connection: RawConnection;
private constructor(conn: RawConnection) {
this.connection = conn;
}
static async connect(url: string): Promise<DbConnection> {
const conn = await rawConnect(url);
return new DbConnection(conn);
}
async query(sql: string): Promise<unknown[]> {
return this.connection.execute(sql);
}
async [Symbol.asyncDispose](): Promise<void> {
await this.connection.close();
console.log("Connection closed");
}
}
// await using — для AsyncDisposable
async function getUsers() {
await using db = await DbConnection.connect("postgres:// localhost/app");
return db.query("SELECT * FROM users"); // соединение закроется автоматически
}
Несколько ресурсов освобождаются в обратном порядке:
TypeScript
async function transfer() {
await using source = await DbConnection.connect("postgres:// source");
await using target = await DbConnection.connect("postgres:// target");
const data = await source.query("SELECT * FROM accounts");
// ... перенос данных ...
// Порядок dispose: target первым, потом source (LIFO)
}
Фабричная функция, возвращающая Disposable:
TypeScript
function createTempDir(): Disposable & { path: string } {
const path = mkdtempSync("/tmp/app-");
console.log(`Created: ${path}`);
return {
path,
[Symbol.dispose]() {
rmSync(path, { recursive: true });
console.log(`Removed: ${path}`);
},
};
}
function buildProject() {
using tmp = createTempDir(); // используем tmp.path для временных файлов
// при выходе из функции папка удалится
}
Реальные кейсы
Лок на ресурс:
TypeScript
class Mutex {
private locked = false;
private waiters: (() => void)[] = [];
async acquire(): Promise<Disposable> {
while (this.locked) {
await new Promise<void>((resolve) => this.waiters.push(resolve));
}
this.locked = true;
return {
[Symbol.dispose]: () => {
this.locked = false;
this.waiters.shift()?.();
},
};
}
}
const mutex = new Mutex();
async function criticalSection() {
using lock = await mutex.acquire(); // безопасная работа с общим ресурсом
// лок автоматически отпустится
}
Ошибка: забыть using и потерять автоочистку:
TypeScript
// ❌ Обычный const — dispose не вызовется
const file = new FileHandle("data.txt"); // file[Symbol.dispose]() не вызовется автоматически
// ✅ using — dispose вызовется при выходе из скоупа
using file2 = new FileHandle("data.txt");
Резюме
using и await using автоматически вызывают [Symbol.dispose]() или [Symbol.asyncDispose]() при выходе из блока. Это устраняет целый класс багов с утечкой ресурсов. Для работы нужен TypeScript 5.2+ и target ES2022 или полифилл для Symbol.dispose.