Ключевое слово using в TypeScript и явное управление ресурсами

Утечки памяти в продакшене часто возникают из-за одной ошибки: разработчики получают ресурсы, но не освобождают их.

Соединения с базами данных остаются открытыми после ошибок. Дескрипторы файлов потребляют системные ресурсы. WebSocket-клиенты остаются подключенными к мертвым серверам. Это происходит, когда вы полагаетесь на ручную очистку в блоках finally.

Ключевое слово using в TypeScript решает эту проблему. Оно является частью предложения ECMAScript по явному управлению ресурсами (Explicit Resource Management). Оно гарантирует очистку с помощью паттерна «disposable» (утилизируемый ресурс). Когда ресурс выходит за пределы своей области видимости, TypeScript автоматически вызывает его метод очистки.

Вам больше не нужны ручные блоки finally. Вы больше не забываете об очистке. Вы больше не допускаете утечек соединений.

Как это работает:

  • Ключевое слово using обеспечивает очистку при выходе ресурсов из области видимости.
  • Утилизируемые ресурсы реализуют Symbol.dispose для синхронных задач или Symbol.asyncDispose для асинхронных задач.
  • TypeScript преобразует объявления using в блоки try-finally с автоматическим стеком очистки.
  • Этот паттерн предотвращает утечки, вызванные преждевременными возвратами (return), выброшенными исключениями или забытым кодом.

Используйте using для любого ресурса с определенным жизненным циклом, такого как соединения с базами данных, дескрипторы файлов, блокировки или таймеры.

Механизм использует протокол очистки. Объекты реализуют метод, ключом которого является Symbol.dispose. Когда область видимости завершается обычным путем, через return или из-за исключения, TypeScript вызывает этот метод.

Пример с дескриптором файла:

class FileHandle {
  private handle: number;

  constructor(path: string) {
    this.handle = openFileSync(path);
  }

  [Symbol.dispose]() {
    if (this.handle !== -1) {
      closeFileSync(this.handle);
      this.handle = -1;
    }
  }

  read(buffer: Buffer): number {
    return readSync(this.handle, buffer);
  }
}

function processFile(path: string) {
  using file = new FileHandle(path);
  const buffer = Buffer.alloc(1024);
  file.read(buffer);
}

Очистка выполняется в сгенерированном блоке finally. Она срабатывает, даже если функция выбрасывает ошибку или завершается преждевременно.

TypeScript поддерживает стек очистки. Он освобождает ресурсы в обратном порядке относительно того, как они были получены. Это соответствует естественному порядку зависимостей для большинства логик очистки.

Для асинхронной очистки используйте await using. Это требует, чтобы ресурс реализовывал Symbol.asyncDispose. TypeScript будет ожидать завершения возвращаемого Promise, прежде чем продолжить выполнение.

Три правила успеха:

  • Не выбрасывайте исключения внутри методов очистки. Вместо этого перехватывайте и логируйте ошибки внутри метода.
  • Не используйте асинхронные операции внутри синхронного метода Symbol.dispose. Используйте вместо него Symbol.asyncDispose.
  • Помните, что объявления using имеют область видимости блока. Ресурс внутри блока if будет очищен, когда этот блок завершится, а не когда завершится функция.

Этот паттерн обеспечивает безопасность при незначительных затратах ресурсов во время выполнения.

Source: https://dev.to/jsmanifest/typescript-using-keyword-and-explicit-resource-management-done-right-22pg