В JS/TS появляется детерминированное освобождение ресурсов: не когда GC соберет объект, а при выходе из scope. В production это важно для Node.js-сервисов, SSR, SDK, API clients и shared libraries, где часто забывают вызвать
close, release, rollback или unsubscribe.Идея
Ресурс реализует протокол, а язык вызывает cleanup сам:
*
using - синхронный [Symbol.dispose]()*
await using - асинхронный [Symbol.asyncDispose]()await using дожидается cleanup, поэтому подходит для DB connection, Redis lock, stream, socket, transaction.Production-паттерн: транзакция
class Tx {
committed = false;
async commit() {
await db.commit();
this.committed = true;
}
async [Symbol.asyncDispose]() {
if (!this.committed) await db.rollback();
}
}
async function createOrder(input: OrderInput) {
await using tx = await db.beginTransaction();
const order = await tx.orders.create(input);
await tx.audit.log("order_created", order.id);
await tx.commit();
return order;
}Если
audit.log выбросит ошибку, транзакция будет откатана без ручного try/finally в каждом use case.Важные детали
* dispose вызывается в обратном порядке объявления
* cleanup выполняется при
return и throw* ошибки из основного кода и dispose не теряются, используется
SuppressedError* это не замена GC: GC про память,
using - про внешние ресурсыПрактический совет
Для библиотечного API проектируйте не контракт "не забудь вызвать
.close()", а явный протокол [Symbol.dispose] / [Symbol.asyncDispose]. В TypeScript синтаксис поддерживается с TS 5.2.Предупреждение: если cleanup возвращает Promise, обычный
using не подходит - используйте await using.Вывод:
using и await using делают жизненный цикл ресурсов явным, снижают риск утечек и улучшают надежность async-кода без разрастания try/finally.
