async/await и трансформация в промисы

Общая модель трансформации асинхронных функций

В экосистеме 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 сложнее и ближе к следующей модели:

  • async-функция становится обычной функцией
  • тело функции оборачивается в генератор или state machine
  • выполнение управляется вспомогательной обёрткой asyncToGenerator

Генераторная модель и state machine

В большинстве конфигураций 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(…)
  • продолжение функции выполняется в microtask queue
  • синхронная часть до первого 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 и критично для производительности.


Отличия от Babel-трансформации

SWC и Babel используют схожие идеи, но различаются реализацией:

SWC:

  • Rust-реализация трансформера
  • более агрессивная оптимизация AST
  • минимальные runtime-хелперы
  • часто использует собственные версии asyncToGenerator

Babel:

  • более модульная система плагинов
  • широкое использование regeneratorRuntime
  • более тяжёлый runtime при генераторной модели

Режимы компиляции async в SWC

Поведение зависит от конфигурации .swcrc:

{
  "jsc": {
    "transform": {
      "asyncGenerator": true
    }
  }
}

Возможные стратегии:

  • генераторная трансформация
  • сохранение async/await (если target поддерживает)
  • гибридный режим при современных targets (ES2017+)

Инлайнинг Promise-обёрток

В современных конфигурациях SWC стремится минимизировать overhead:

  • переиспользование helper-функций
  • кэширование runtime
  • сокращение количества .then цепочек
  • оптимизация хвостовых вызовов в генераторах (частично)

Граничные случаи трансформации

Возврат Promise внутри async

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 в сборочных пайплайнах:

  • трансформация async влияет на размер bundle
  • генераторная модель увеличивает количество функций
  • минимизация runtime-хелперов критична для performance
  • target ES2017+ может полностью отключать трансформацию

Типичный компромисс:

  • старые браузеры → generator-based async
  • современные среды → native async/await без трансформации

Итоговая модель исполнения

После всех преобразований SWC приводит async/await к одной из двух форм:

  1. Нативная форма (без трансформации) используется при современном target

  2. Generator-based форма

function f() {
  return _asyncToGenerator(function* () {
    const x = yield expr;
    return x;
  })();
}

Эта модель полностью сохраняет поведение оригинального async/await, опираясь на Promise как базовый механизм асинхронности и генераторы как средство управления состоянием выполнения.