В экосистеме SWC асинхронные функции рассматриваются как синтаксический
сахар над промисами. Независимо от исходного кода с
async/await, на этапе компиляции происходит его понижение
до функций, работающих через Promise и вспомогательные
обёртки.
Ключевая цель трансформации — сохранить семантику выполнения:
await
throw
return
this
Внутренне SWC не исполняет код, а переписывает его в эквивалентную форму, совместимую с более ранними версиями JavaScript-движков.
async функции
Исходный код:
async function fetchData() {
const res = await fetch("/api/data");
return res.json();
}
После трансформации концептуально превращается в:
function fetchData() {
return Promise.resolve(fetch("/api/data"))
.then(res => res.json());
}
Однако реальная трансформация SWC сложнее и ближе к следующей модели:
asyncToGenerator
В большинстве конфигураций SWC использует стратегию преобразования через генераторы.
Примерная форма результата:
function fetchData() {
return _asyncToGenerator(function* () {
const res = yield fetch("/api/data");
return res.json();
})();
}
Здесь вводится ключевая абстракция:
function* заменяет async
yield заменяет await
_asyncToGenerator управляет выполнением генератора
_asyncToGenerator
Вспомогательная функция, генерируемая SWC (или подключаемая из runtime), выполняет оркестрацию промисов:
function _asyncToGenerator(fn) {
return function () {
const gen = fn.apply(this, arguments);
return new Promise((resolve, reject) => {
function step(key, arg) {
let info;
let value;
try {
info = gen[key](arg);
value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
Promise.resolve(value).then(
val => step("next", val),
err => step("throw", err)
);
}
}
step("next");
});
};
}
Семантика:
yield превращается в ожидание промиса
throw
await в выражения yield
Каждый оператор await становится точкой приостановки
исполнения.
Исходный код:
async function load() {
const a = await getA();
const b = await getB(a);
return a + b;
}
После трансформации:
function load() {
return _asyncToGenerator(function* () {
const a = yield getA();
const b = yield getB(a);
return a + b;
})();
}
Особенность: порядок выполнения строго сохраняется, так как генератор
приостанавливается на каждом yield.
try/catch)
SWC сохраняет семантику исключений через try/catch внутри
генератора.
Исходный код:
async function safe() {
try {
const data = await fetch("/api");
return data.json();
} catch (e) {
return null;
}
}
После трансформации:
function safe() {
return _asyncToGenerator(function* () {
try {
const data = yield fetch("/api");
return data.json();
} catch (e) {
return null;
}
})();
}
Ошибки:
reject промиса вызывается gen.throw
catch
catch отсутствует — промис отклоняется
this и замыкания
SWC сохраняет поведение this через .apply(this,
arguments):
function example() {
return _asyncToGenerator(function* () {
yield this.step();
return this.value;
}).apply(this);
}
Это критично для методов объектов:
const obj = {
x: 10,
async method() {
return this.x + (await Promise.resolve(5));
}
};
После трансформации:
method: function method() {
return _asyncToGenerator(function* () {
return this.x + (yield Promise.resolve(5));
}).apply(this);
}
Хотя SWC не изменяет модель event loop, важно учитывать, что:
await всегда эквивалентен
Promise.resolve().then(…)
await выполняется немедленно
Пример:
async function test() {
console.log(1);
await null;
console.log(2);
}
Эквивалент:
function test() {
console.log(1);
return Promise.resolve(null).then(() => {
console.log(2);
});
}
await и последовательность
SWC строго сохраняет последовательное выполнение:
async function seq() {
const a = await step1();
const b = await step2(a);
const c = await step3(b);
return c;
}
Трансформация:
function seq() {
return _asyncToGenerator(function* () {
const a = yield step1();
const b = yield step2(a);
const c = yield step3(b);
return c;
})();
}
Каждый шаг блокирует следующий до завершения промиса.
Важно различать последовательный и параллельный вызов:
async function parallel() {
const a = step1();
const b = step2();
return await a + await b;
}
После трансформации:
function parallel() {
return _asyncToGenerator(function* () {
const a = step1();
const b = step2();
return (yield a) + (yield b);
})();
}
Здесь видно:
step1 и step2 запускаются сразу
yield
Это поведение не меняется SWC и критично для производительности.
SWC и Babel используют схожие идеи, но различаются реализацией:
SWC:
asyncToGenerator
Babel:
regeneratorRuntime
Поведение зависит от конфигурации .swcrc:
{
"jsc": {
"transform": {
"asyncGenerator": true
}
}
}
Возможные стратегии:
async/await (если target поддерживает)
В современных конфигурациях SWC стремится минимизировать overhead:
.then цепочек
async function x() {
return Promise.resolve(1);
}
После трансформации:
function x() {
return _asyncToGenerator(function* () {
return Promise.resolve(1);
})();
}
Важно: двойного оборачивания не происходит — Promise не “распаковывается”.
await в выражениях
async function calc() {
return (await a()) + (await b());
}
Становится:
function calc() {
return _asyncToGenerator(function* () {
return (yield a()) + (yield b());
})();
}
Каждый yield изолирован, но операция остаётся
последовательной.
При использовании SWC в сборочных пайплайнах:
Типичный компромисс:
После всех преобразований SWC приводит async/await к одной
из двух форм:
Нативная форма (без трансформации) используется при современном target
Generator-based форма
function f() {
return _asyncToGenerator(function* () {
const x = yield expr;
return x;
})();
}
Эта модель полностью сохраняет поведение оригинального
async/await, опираясь на Promise как базовый
механизм асинхронности и генераторы как средство управления состоянием
выполнения.