TGViewer
SuperOleg dev notes SuperOleg dev notes @super_oleg_dev · 1.75K subscribers
Post #131 814
Переходим к телепортации промисов с сервера на клиент.

В первую очередь, мне очень комфортно работать с паттерном Deferred и он отлично подходит для этой задачи:


class Deferred {
constructor() {
this.promise = new Promise((resolve, reject) => {
this.resolve = (data) => {
this.resolveData = data;
resolve(data);
};
this.reject = (reason) => {
this.rejectReason = reason;
reject(reason);
};
});
}

isResolved() {
return typeof this.resolveData !== 'undefined';
}

isRejected() {
return typeof this.rejectReason !== 'undefined';
}
}

На сервере, на каждое асинхронное действие, которое мы пометим как отложенное, необходимо создать свой экземпляр Deferred промиса, и выполнить его resolve/reject по завершению соответствующей асинхронной задачи.

Этот паттерн позволит не ждать сайд-эффект сразу, но подписаться на его выполнение позже, псевдо-код:

async function runDeferredAction() {
deferred = new Deferred()

// допустим этот асинхронный метод выполняется 5 секунд
someLongAction
.then(deferred.resolve)
.catch(deferred.reject)
}

async function render() {
// запускаем сайд эффекты, runDeferredAction резолвится сразу и не блокирует ответ
await promiseAll([
runAnyAction(),
runAnyAction(),
runDeferredAction()
])

// запускаем рендер в стрим
reactRenderToStream()

// ждем отложенный экшен
await deferred.promise

// телепортируем промис
...

// закрываем стрим
closeStream()
}

В качестве механизма для отложенной загрузки данных показалось хорошим решением использовать уже существующие трамвайные Actions - наш основной инструмент для создания и выполнения сайд-эффектов.

В отличие от функций - загрузчиков данных в Remix или SvelteKit, на одну страницу трамвай позволяет запустить множество параллельных экшенов, также мы обычно не используем данные из экшенов напрямую, а передаем их с сервера на клиент через трамвайные сторы.

Для новых deferred экшенов оказалось проще все-таки использовать их напрямую для получения данных, что бы не усложнять “телепортацию” и не добавлять новых API (еще раздумываю над этим решением).

Итак, у нас есть набор страничных экшенов, некоторые из них помечены как deferred, опишу механизм телепортации:

- трамвай не ждет выполнения deferred экшенов перед рендерингом страницы на сервере
- на каждый такой экшен создается инстанс new Deferred(), который будет зарезолвлен после резолва самого экшена
- на клиент в head передается инлайн скрипт вида <script>window.DEFERRED_MAP['actionName'] = new Deferred()</script> - то есть название экшена и становится уникальным ключем и таким “мостом” между сервером и клиентом
- после резолва экшена, на клиент передается инлайн скрипт с резолвом промиса с соответствующими данными (их обернул в защиту от XSS атак) - <script>window.DEFERRED_MAP['actionName'].resolve(SANITIZED_ACTION_DATA)</script>

Таким образом, промис на клиенте будет зарезолвлен практически сразу после того как это произойдет на сервере.
tramvai.dev Actions | tramvai Explanation
  • 👍 9
  • 🔥 2
More from @super_oleg_dev
  1. Sep 10, 2026Посмотреть на работу анимаций и скролла можно в нашей демке, основанной на примере Remix ф…
  2. Sep 10, 2026Далее, про скролл. В принципе для SPA-приложений это база, если при переходах на разные ст…
  3. Sep 10, 2026Раз уж меня прорвало, вспомню ещё один интересный кейс. Решали примерно в одно время две п…
  4. Sep 10, 2026Вместо тысячи слов, пример работы и сразу разметка, которая долетает в HTML - и наш механи…
  5. Sep 10, 2026Когда-то давно рассказывал про интеграцию стриминга и deferred загрузки данных в Tramvai -…
  6. Sep 7, 2026TIL, небольшой, но интересный кейс! Делал кастомный анализ бандла через Claude, пишет мне…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →