Любой JavaScript-разработчик сталкивается с эстетическим и архитектурным компромиссом при инициализации сложных констант. Представьте типичную задачу: прочитать конфигурационный файл, распарсить JSON, а в случае ошибки подставить значение по умолчанию. Казалось бы, тривиальная операция. Но поскольку конструкция try/catch в JavaScript является инструкцией (Statement), а не выражением (Expression), она не умеет возвращать значение наружу.
В результате мы вынуждены либо объявлять мутабельную переменную let config перед блоком, открывая лазейку для случайных переопределений дальше по коду, либо городить громоздкие анонимные самовызывающиеся функции (IIFE). Концепция блочных выражений (Block Expressions) и официальное предложение TC39 do-expressions призваны стереть эту тридцатилетнюю историческую границу, приблизив эргономику JavaScript к современным языкам вроде Rust и Kotlin.
Проклятие мутабельного let и костыли с анонимными IIFE
Разделение на инструкции и выражения досталось JavaScript от C и Java. Выражение всегда вычисляется в значение (например, 2 + 2, вызов функции или тернарный оператор), поэтому его можно присвоить константе или передать аргументом. Инструкции же (if/else, switch, for, try/catch) лишь управляют потоком исполнения программы, возвращая пустоту.
Из-за этого архитектурного барьера разработчики годами прибегают к двум сомнительным практикам:
- Мутабельные переменные: объявление
letза пределами ветвления разрушает концепцию иммутабельности данных. Читатель кода не может быть уверен, что переменная останется неизменной ниже по тексту. - Анонимные замыкания (IIFE): обертка
const config = (() => { try { ... } catch { ... } })()решает проблему иммутабельности, но создает лишний контекст функции, плодит замыкания и создает микроскопические накладные расходы на создание стекового фрейма в рантайме.
Как do-выражения возвращают результат последнего действия
Предложение do-expressions устраняет этот разрыв с помощью лаконичного синтаксиса do { ... }. Блок кода превращается в полноценное выражение, результатом которого становится значение последней вычисленной инструкции:
// До: мутабельный let и размазанная инициализация
let dbConfig;
try {
const fileContent = fs.readFileSync('config.json', 'utf8');
dbConfig = JSON.parse(fileContent);
} catch {
dbConfig = { host: 'localhost', port: 5432 };
}
// После: чистая иммутабельная константа с do-expressions
const dbConfig = do {
try {
const fileContent = fs.readFileSync('config.json', 'utf8');
JSON.parse(fileContent); // Результат блока
} catch {
({ host: 'localhost', port: 5432 }); // Значение по умолчанию
}
};
Особенно ярко мощь блочных выражений проявляется в декларативном UI-коде (React, Vue, Solid). Вместо «ада тернарных операторов», когда вложенные условия condition ? (sub ? a : b) : c превращают разметку компонента в нечитаемую кашу, разработчик получает структурированные ветвления прямо внутри JSX:
// Читаемый рендеринг состояний в JSX без костыльных функций
export const OrderStatusBanner = ({ orderStatus, isPremium }) => (
<div className="status-container">
{do {
if (orderStatus === 'pending') {
<PendingSpinner label="Ожидаем подтверждения оплаты" />;
} else if (orderStatus === 'failed') {
<RetryPaymentCard reason="Недостаточно средств" />;
} else {
<SuccessReceipt isVip={isPremium} />;
}
}}
</div>
);
Внутренности V8, статус предложения в TC39 и альтернативы
Самое ироничное в этой истории то, что на уровне низкоуровневых движков всё давно готово. В абстрактном синтаксическом дереве (AST) движка Google V8 блоки кода всегда имели внутреннее значение завершения (completion value) — именно поэтому консоль браузера возвращает результат последней строчки при вставке фрагмента скрипта. Байткод-генератору Ignition практически не требуются новые опкоды для поддержки do { ... }.
Главной загвоздкой в комитете TC39 долгое время оставались синтаксические коллизии. Парсеру JavaScript трудно отличить объектный литерал { key: value } от блока кода со словарными метками (labels). Ключевое слово do снимает эту двусмысленность, явно указывая парсеру на режим вычисляемого блока.
Пока предложение проходит фазу исследований в TC39 (Stage 1 / Stage 2), разработчики могут оценить удобство синтаксиса уже сегодня с помощью плагина @babel/plugin-proposal-do-expressions. Полноценное принятие стандарта сделает код на JavaScript чище, избавит интерфейсы от вложенных тернарников и закрепит главенство иммутабельных данных.
