В марте 2023 года было представлено новое предложение ECMAScript, обещающее значительно облегчить жизнь разработчиков JavaScript. Предложение направлено на то, чтобы упростить освобождение ресурсов, которые должны быть освобождены разработчиком явно, например, закрытие открытого файла или закрытие потока.

Черпая вдохновение из with statement Python, with C# и try-with-resources Java, предложение предлагает ввести аналогичную концепцию в JavaScript.

В этой статье я собираюсь изучить это предложение и то, как оно обещает улучшить ситуацию 🔥

Устройтесь поудобнее, расслабьтесь, возьмите свой кофе ☕ и начнем!

Разминка

В этой статье я последовательно буду ссылаться на управление ресурсами. Однако, прежде чем углубляться в тему, давайте вернемся к той же странице.

Ресурсы относятся к любому объекту с временем существования, независимо от того, контролируется ли он «неявно» сборщиком мусора или «явно» разработчиком, например, закрытие дескриптора файла. И достаточно очевидно, что процесс управления этими ресурсами называется Управление ресурсами. Однако сегодня нас интересует только явный вид!

У нас сейчас круто? … Давайте начнем!

Что мы пытаемся решить

Чтобы лучше понять, что мы получим от добавления этой функции, давайте обсудим пример.

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

const fs = require('fs').promises;

async function openFileAndDoAwesomeStuff(){

    try {
        var handle = await fs.open('/path/to/file', 'r');
        doAwesomeStuff();
      } finally {
          if(handle){
             await handle.close();
          }
      }
}

openFileAndDoAwesomeStuff();

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

Проблема здесь в том, что нам пришлось написать много шаблонного кода только для того, чтобы открыть файл и обеспечить его безопасное закрытие.

Хорошо, вы можете сказать: «Это не имеет большого значения», и вы, вероятно, правы, но давайте представим, что мы собираемся открыть 2 файла вместо одного.

const fs = require('fs').promises;

async function open2FilesAndDoSomeComplicatedStuff() {
    try {
        var first = await fs.open('/path/to/first', 'r');
        try {
            var second = await fs.open('/path/to/second', 'r');
            doSomeComplicatedStuff();
        } finally {
            if (second) {
                await second.close();
            }
        }
    } finally {
        if (first) {
            await first.close();
        }
    }
}

open2FilesAndDoSomeComplicatedStuff();

Вещи начали выглядеть уродливыми! ты не думаешь?

Итак, проблема здесь заключается в сложности управления ресурсами (да!... Это называется Явное управление ресурсамиManagement) и обеспечения безопасного их выпуска!

Но держите телефон, это не останавливается на этом уродливом коде. Предложение включало в себя несколько замечательных моментов, которые можно добавить поверх этого утомительного кода, вот наиболее важные из них:

  1. Непоследовательность в шаблонах управления ресурсами: для каждого типа ресурсов у нас есть разные способы их освобождения. Например, для Iterator у нас есть iterator.return()для NodeJS FileHandles у нас есть handle.close() и для чтения потока WHATWG мы получили reader.releaseLock(); …….
  2. Избавьтесь от обычной головной боли при управлении несколькими ресурсами: мы можем вызвать проблемы при управлении несколькими ресурсами, например, высвобождая некоторые ресурсы, в то время как другой все еще зависит от них, или сталкиваясь с ошибкой, прежде чем действительно сможем высвободить все ресурсы. ….
const a = ...;
const b = ...;
try {
  ...
}
finally {
  a.close(); // Oops, issue if `b.close()` depends on `a`.
  b.close(); // Oops, `b` never reached if `a.close()` throws.
}

3. Ограничение объема ресурсов: обычно вы не хотите использовать ссылку на ресурсы после ее выпуска, но это может произойти по ошибке, если мы все еще находимся в том же объеме. Было бы очень полезно, если бы мы нашли способ предотвратить это!

const fs = require('fs').promises;
const handle = await fs.open('/path/to/file', 'r');

handle.close();

// handle shouldn't be used but still in scope
handle .... 

Хорошо, вы могли бы сказать, что можете избежать всего этого, если будете уделять немного внимания во время кодирования, и я не собираюсь этого отрицать. Однако, если у нас есть способ облегчить себе жизнь, почему бы и нет?!

Введите явное управление ресурсами ECMAScript

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

Позвольте мне переписать наш первый пример, но на этот раз с использованием ключевого слова using:

const fs = require('fs').promises;

async function openFileAndDoAwesomeStuff(){
    using handle = await fs.open('/path/to/file', 'r');
    doAwesomeStuff();
}

openFileAndDoAwesomeStuff();

Вот так, вот так, никому не нужно вручную закрывать дескриптор, он волшебным образом закроется, как только мы выйдем из области видимости (функции) либо нормально, либо если были выброшены какие-то ошибки.

Кроме того, мы можем связать несколько ресурсов вместе с одним оператором using, примерно так:

{
  using x = getSomething(), y = getThatOtherThing();
}

Я люблю это до сих пор,

Теперь вам может быть интересно, как usingkeyword обрабатывает асинхронные/ожидающие операции. Не бойтесь, он также поддерживает async/await:

// an asynchronously-disposed, block-scoped resource
await using x = expr1;            // resource w/ local binding
await using y = expr2, z = expr4; // multiple resources

Но как это должно работать?

Посмотрим правде в глаза, магии не существует (облом… верно?). Итак, откуда using знает, как высвободить добытые нами ресурсы?

Чтобы иметь возможность использовать ключевое слово using, объект должен реализовать новый Общеизвестный символ с именем @@dispose и/или @@asyncDispose, и всякий раз, когда using решает высвободить полученные ресурсы, он будет вызывать этот символ. Как следует из названий, @@dispose будет использоваться с синхронными ресурсами, а @@asyncDispose отвечает за высвобождение ресурсов при использовании await using.

const myNiceResources = { [Symbol.dispose]() {} };

// Will throw an error because res doesn't have @@asyncDispose
await using res;

В этом случае, поскольку myNiceResources не имеет @@asyncDispose, код выдаст ошибку при попытке использовать с ним await using.

Волнуюсь, когда я смогу использовать эту функцию?

На момент написания этой статьи (22 июля 2023 г.) предложение находится на этапе 3 (также известном как «Кандидат»), что означает, что оно созрело и готово к отзывам от реализаций и пользователей. Хотя функция еще не полностью завершена, сейчас она находится на продвинутой стадии разработки.

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

Для получения дополнительной информации об этапах предложений ECMA вы можете проверить это Этапы предложения ECMAScript.

Будьте уверены, поскольку предложение достигло стадии кандидата, использование этой функции быстро становится реальностью. Следите за дальнейшими обновлениями! 🔥

TL; DR

Предложение ECMAScript Explicit Resource Management направлено на упрощение обработки ресурсов в JavaScript за счет введения ключевого слова using. Это ключевое слово позволяет разработчикам получать ресурсы в пределах определенной области и автоматически освобождает их при выходе из этой области, устраняя необходимость в ручном управлении ресурсами.

Ресурсы:

tc39/proposal-explicit-resource-management: Явное управление ресурсами ECMAScript (github.com)

Компаратор спецификаций языка ECMAScript (arai-a.github.io)

Стандарт стримов (whatwg.org)

API веб-потоков | Документация Node.js v20.5.0 (nodejs.org)

8. Составные операторы — документация по Python 3.11.4

Утверждение о попытке использования ресурсов (Учебные руководства по Java™ › Основные классы Java › Исключения) (oracle.com)

утверждение использования — обеспечить правильное использование одноразовых объектов | Обучение Майкрософт

Файловая система | Документация Node.js v20.4.0 (nodejs.org)

HTTP | Документация Node.js v20.4.0 (nodejs.org)

Поток | Документация Node.js v20.4.0 (nodejs.org)

Символ — JavaScript | MDN (mozilla.org)

Этапы предложения ECMAScript