SWC обрабатывает генераторы и итераторы на этапе транспиляции как часть
понижения современных возможностей ECMAScript до более ранних версий
JavaScript. Основная цель трансформации заключается в сохранении
семантики yield, протокола итераторов и асинхронной
итерации при отсутствии нативной поддержки в целевой среде выполнения.
Итераторы в ECMAScript основаны на соглашении о наличии метода
next(), возвращающего объект вида:
{ value: any, done: boolean }
Объекты, реализующие Symbol.iterator, предоставляют
итератор, который используется конструкциями for…of,
оператором расширения и деструктуризацией.
Генераторы добавляют поверх этого механизма приостановку выполнения
функции через yield, превращая линейный поток исполнения в
управляемую конечную автоматную модель.
function* gen() {
yield 1;
yield 2;
}
На уровне языка это выглядит как функция с внутренним состоянием, но в исходном JavaScript до трансформации отсутствует явная модель состояния.
SWC преобразует генераторы в функции, возвращающие объект-итератор, где
логика выполнения представлена конечным автоматом (state machine).
Каждое yield становится точкой разбиения выполнения с
сохранением контекста.
Ключевая идея трансформации заключается в следующем:
yield становится переходом между состояниями
next() с переключателем
состояния
Пример упрощённого результата трансформации:
function gen() {
let state = 0;
return {
next() {
while (true) {
switch (state) {
case 0:
state = 1;
return { value: 1, done: false };
case 1:
state = 2;
return { value: 2, done: false };
case 2:
return { value: undefined, done: true };
}
}
}
};
}
Реальная реализация SWC значительно сложнее, поскольку учитывает
исключения, вложенные блоки, try/catch/finally, а также
yield*.
yield и сохранение контекста
Каждый yield требует фиксации текущего состояния
выполнения:
SWC вводит внутреннюю структуру состояния, которая часто представляется
как объект-контекст. При каждом вызове next() выполнение
продолжается с последнего сохранённого состояния.
Особое значение имеет тот факт, что yield является
выражением:
const x = yield 10;
Значение x определяется аргументом, переданным в
next(value). Это требует от трансформированного кода
поддержки двунаправленного обмена данными между итератором и внешним
вызовом.
yield* и делегирование итераторов
Конструкция:
function* a() {
yield* b();
}
разворачивается в цикл делегирования, где внешний генератор проксирует значения внутреннего итератора.
Логика трансформации включает:
b()
next() у вложенного итератора
done: true
Упрощённая модель:
let inner = b();
while (true) {
let r = inner.next();
if (r.done) break;
yield r.value;
}
SWC при этом учитывает необходимость сохранения состояния внешнего генератора на время работы внутреннего.
try/catch/finally
Одной из самых сложных частей трансформации является корректная поддержка исключений.
Генератор может быть приостановлен внутри try, а затем
возобновлён с передачей ошибки через throw().
SWC строит расширенную state machine, в которой:
try имеет собственную область состояний
finally гарантированно исполняется при завершении или
исключении
throw() маршрутизирует выполнение в соответствующий
обработчик
Пример поведения:
function* g() {
try {
yield 1;
} finally {
yield 2;
}
}
После трансформации требуется гарантировать выполнение
finally даже при внешнем прерывании итератора.
for…of
Конструкция:
for (const x of gen()) {}
опирается на Symbol.iterator. SWC не изменяет сам синтаксис
цикла, но гарантирует, что преобразованный генератор возвращает
корректный итератор.
В случаях отсутствия поддержки Symbol.iterator в целевой
среде SWC может подключать полифилы через @swc/helpers.
for await…of
Асинхронные генераторы:
async function* ag() {
yield await fetchData();
}
трансформируются в структуру, основанную на Promise и
очереди выполнения.
Ключевое отличие:
next() возвращает Promise<{value, done}>
yield оборачивается в асинхронную цепочку
Также поддерживается протокол Symbol.asyncIterator,
необходимый для for await…of.
Внутренне SWC использует абстракцию конечного автомата, где:
yield, return,
throw
Это позволяет унифицировать обработку:
Несмотря на различие синтаксиса, все они сводятся к управляемой машине состояний с сохранением контекста выполнения.
Лексическое окружение функции сохраняется через замыкания. Однако
переменные, изменяющиеся между yield, должны быть вынесены
в состояние, доступное между вызовами next().
Это приводит к следующей трансформационной стратегии:
var/let/const переводятся в поля контекста
yield
Особую сложность создают:
Поведение трансформации генераторов определяется настройками:
target)
jsc.transform
externalHelpers)
Пример логики настройки:
При трансформации SWC может подключать вспомогательные функции, обеспечивающие корректную работу итераторов. Эти хелперы обеспечивают:
Symbol.iterator
Роль runtime критична для сохранения поведения, идентичного нативному исполнению, особенно при вложенных генераторах и асинхронных цепочках.
Некоторые конструкции требуют дополнительной нормализации:
while(true) yield)
yield
yield* с асинхронностью
В таких случаях SWC увеличивает число состояний и усложняет переходы, чтобы избежать потери семантики.
Несмотря на высокую точность понижения, существуют ограничения:
Тем не менее логическая эквивалентность поведения сохраняется, что позволяет использовать генераторы в средах без их нативной поддержки.