
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-канале ↩
