Deno — это компания, ориентированная на развитие JavaScript. Мы верим, что JavaScript должен быть простым, мощным и приятным в использовании. Deno стремится модернизировать JavaScript и инструменты вокруг него, предоставляя нативную поддержку TypeScript и стирая границы между серверным и браузерным JavaScript с помощью API, основанных на веб-стандартах. Поэтому мы активно участвуем в развитии JavaScript-экосистемы и в работе комитетов по стандартам, таких как TC39, — ведь мы хотим сделать JavaScript лучше и эффективнее для всех.

Недавно прошла 108-я встреча TC39, на которой было продвинуто 9 предложений на разные стадии стандартизации — от сырых идей (Стадия 0) до полностью утвержденных возможностей (Стадия 4).

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

Стадия 4

  • Explicit Resource Management (using)

  • Array.fromAsync

  • Error.isError

Стадия 3

  • Immutable ArrayBuffer

Стадия 2

  • Random.Seeded

  • Number.prototype.clamp

Стадия 1

  • Сохранение конечных нулей

  • Сравнения

  • Функции произвольных значений

❯ Стадия 4

Explicit Resource Management (using)

Новая конструкция using (и ее асинхронный вариант await using) добавляет детерминированное освобождение ресурсов — идея, вдохновленная такими языками программирования, как C# и Python.

Объекты могут реализовать специальные методы [Symbol.dispose]() или [Symbol.asyncDispose](), которые будут автоматически вызываться при выходе из блока кода.

Пример использования:

class FileHandle {
  constructor(name) {
    this.name = name; /* открыть файл... */
  }
  [Symbol.dispose]() {
    console.log(`${this.name} closed`); /* закрыть файл */
  }
}
function readFile() {
  {
    using file = new FileHandle("data.txt");
    // Чтение файла...
  }
  // Здесь автоматически вызывается `file.[Symbol.dispose]()`
}
readFile(); // "data.txt closed"

Это обеспечивает очистку ресурсов (например, закрытие файлов или потоков) даже при возникновении исключения внутри блока. Такой подход делает управление ресурсами проще и надежнее.

Эта возможность уже поддерживается в Chrome 134, Firefox 134 и Deno v2.3.

В Deno ключевое слово using уже можно использовать для управления ресурсами — такими как файловые дескрипторы (Deno.File), сетевые соединения (Deno.Conn) и т.д. Например, в приведенном ниже примере HTTP-сервер автоматически останавливается после выполнения запроса:

using server = Deno.serve({ port: 8000 }, () => {
  return new Response("Hello, world!");
});

const response = await fetch("http://localhost:8000");
console.log(await response.text()); // "Hello, world!"

// Здесь сервер автоматически останавливается благодаря ключевому слову `using`

Команда Deno также заинтересована в использовании ключевого слова using для упрощения передачи асинхронного контекста. На самом деле, текущая реализация поддержки асинхронного контекста в Deno (стадия 2) уже позволяет, например, автоматически добавлять HTTP-информацию в console.log. Однако сейчас, чтобы создать новый "спан" (span) для отслеживания выполнения, каждый раз приходится создавать отдельную функцию и запускать ее вручную. Это неудобно:

async function doWork() {
  const parent = tracer.startSpan("doWork");
  return parent.run(async () => {
    console.log("doing some work...");
    return true;
  });
}

Команда Deno предлагает использовать disposable-версию AsyncContext.Variable, что позволит с помощью ключевого слова using упростить этот код:

async function doWork() {
  using parent = tracer.startActiveSpan("doWork");
  console.log("doing some work...");
  return true;
}

Как видите, кода стало значительно меньше.

Array.fromAsync

Array.fromAsync похож на Array.from, но работает с асинхронными итерируемыми объектами и возвращает Promise, который разрешается в итоговый массив. Он также поддерживает функцию преобразования и параметр thisArg, как и Array.from.

Например, если у нас есть асинхронный генератор значений, то мы можем написать следующее:

async function* generate() {
  yield await Promise.resolve(1);
  yield await Promise.resolve(2);
}
const nums = await Array.fromAsync(generate()); // [1, 2]

В этом примере Array.fromAsync(generate()) возвращает промис, который разрешается в массив [1, 2] после того, как все значения будут получены. Это заметно облегчает и делает понятнее типичные способы работы с асинхронными коллекциями.

Array.fromAsync уже поддерживается во всех браузерах, а также в Deno v1.38 и Node.js v22.

Error.isError

Error.isError(value) — новый встроенный метод для надежного определения объектов ошибок. Он возвращает true, если value — любой тип ошибки (включая ошибки из других контекстов выполнения или наследуемые ошибки), и false в остальных случаях. Например:

Error.isError(new TypeError("oops")); // true
Error.isError({ name: "TypeError", message: "oops" }); // false

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

Error.isError поддерживается во всех браузерах, а также в Deno v2.2.

❯ Стадия 3

Иммутабельный ArrayBuffer

Эта возможность находится на стадии 3 и вводит два новых метода: transferToImmutable() и sliceToImmutable(). Вызов transferToImmutable() перемещает данные из текущего буфера в новый неизменяемый буфер и отключает (detaches) оригинал. Например:

let buf = new ArrayBuffer(100);
let imm = buf.transferToImmutable();
// buf теперь отключен (его byteLength равен 0), а imm — новый неизменяемый ArrayBuffer длиной 100
console.log(buf.byteLength, imm.byteLength); // 0, 100

// Попытка изменить imm вызывает TypeError
imm[0] = 1; // TypeError: Cannot modify an immutable ArrayBuffer

Аналогично, метод sliceToImmutable(start, end) создает неизменяемую копию буфера указанного диапазона. Неизменяемые буферы нельзя отключить или изменить — благодаря этому передача бинарных данных (например, между потоками или воркерами) становится надежнее и эффективнее.

В Deno планируют использовать эту возможность для оптимизации различных API, которые принимают байтовые массивы — таких как new Response() или Deno.writeFile(). Это поможет сократить лишние копирования и улучшить производительность при работе с бинарными данными.

❯ Стадия 2

Random.Seeded

Текущие методы генерации псевдослучайных чисел (например, Math.random()) автоматически инициализируются (seeded), из-за чего результат не повторяется между запусками или разными контекстами выполнения. Однако бывают ситуации, когда нужен воспроизводимый набор произвольных значений.

В рамках этого предложения вводится новый класс SeededPRNG, который обеспечивает воспроизводимую генерацию произвольных чисел за счет возможности задания начального значения. Создаем объект Random.Seeded(seedValue) и используем его метод .random() вместо Math.random(), например:

const prng = new Random.Seeded(42);
for (let i = 0; i < 3; i++) {
  console.log(prng.random());
  // При seed === 42 при каждом запуске выводится одна и та же последовательность
}

Это пригодится в играх и симуляциях, где важна повторяемость. Кроме того, можно создавать новые начальные значения или копировать состояние существующего генератора для более сложных задач.

Number.prototype.clamp

Функция Number.prototype.clamp(min, max) (ранее известная как Math.clamp) возвращает число в диапазоне от min до max. Например:

(5).clamp(0, 10); // 5

(-5).clamp(0, 10); // 0 (ограничено снизу - 0)
(15).clamp(0, 10); // 10 (ограничено сверху - 10)

Если min больше max, функция выбрасывает исключение RangeError. Это позволяет избежать громоздких конструкций вроде Math.min(Math.max(x, min), max) и делает код понятнее.

❯ Стадия 1

Сохранение конечных нулей

Новые параметры форматирования в Intl.NumberFormat позволят сохранять или удалять конечные нули в отформатированных числах. Это поможет показывать десятичные числа в более удобном и наглядном виде (например, суммы денег).

Настройка trailingZeroDisplay: "auto" (по умолчанию) сохраняет нули в соответствии с заданной точностью, а "stripIfInteger" убирает их, если число целое. Например:

// Сохраняем два знака после запятой (автоматическое сохранение нулей):
new Intl.NumberFormat("en", {
  minimumFractionDigits: 2,
  trailingZeroDisplay: "auto",
})
  .format(1.5); // "1.50"

// Убираем нули, если они не нужны:
new Intl.NumberFormat("en", {
  minimumFractionDigits: 0,
  trailingZeroDisplay: "stripIfInteger",
})
  .format(2); // "2"  (не "2.0")

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

Сравнения

Предложение Comparisons направлено на стандартизацию способа создания наглядного отображения значений в JavaScript — аналогично util.inspect в Node.js или тому, как тестовые фреймворки показывают отличия (diff).

Цель — обеспечить тестовым библиотекам и консольным инструментам единый и согласованный способ генерации отличий, особенно в разных средах выполнения и контекстах.

Функции случайных значений

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

Методы из пространства Random могут не только генерировать произвольные числовые значения, но и работать с коллекциями — принимать их и возвращать.

Вот несколько примеров:

// Случайное целое число от -5 до 5
Random.int(-5, 5); // -1

// Случайное число от 0 до 10
Random.number(0, 10); // 8

// Случайное число от 0 до 5 с шагом 0.1
Random.number(0, 5, 0.1); // 1.1

// Случайный выбор n элементов из массива
const name = Random.take(["Alice", "Bob", "Carol"], 2); // ['Alice', 'Bob']

// С возвратом (элементы могут повторяться)
Random.take(["Alice", "Bob", "Carol"], 2, { replace: true }); // ['Alice', 'Alice']

// С учетом весов
Random.take(["Alice", "Bob", "Carol"], 2, { weights: [1, 1, 5] }); // ['Alice', 'Bob']

// Случайный выбор одного элемента
Random.sample(["Alice", "Bob", "Carol"]); // 'Bob'

// Тасование массива на месте
Random.shuffle([1, 2, 3, 4]); // [4,2,1,3]

// Возврат нового перемешанного массива
const shuffled = Random.toShuffled([1, 2, 3, 4]);

Цель — сделать работу с произвольными значениями надежнее и чище, сократив количество типичных ошибок (например, промахов на единицу) и повысив предсказуемость кода.

❯ Планы на будущее

TC39 продолжает развивать и улучшать JavaScript, адаптируя язык под нужды современных разработчиков. Команда Deno поддерживает веб-стандарты и активно участвует в этих обсуждениях, чтобы улучшать сам язык и упрощать работу с ним — особенно в среде Deno (достаточно вспомнить поддержку async context propagation и встроенный OpenTelemetry API).

Следующее заседание TC39, на котором продолжится обсуждение этих предложений, запланировано на конец сентября.


Новости, обзоры продуктов и конкурсы от команды Timeweb.Cloud - в нашем Telegram-канале

Комментарии (0)