TGViewer
Khraks dev Khraks dev @khraks_dev · 108 subscribers
Post #29 383
# Schedule ⏲️
Schedule — это абстракция для управления повторением эффектов, которая определяет, сколько раз и с какими интервалами операция должна выполняться или перезапускаться. Например в Effect она используется для определения расписаний при обработке ошибок (retry) или при обычном повторении эффектов(repeat)

В Effect они реализованы следующими компонентами:
- Schedule - начальное значение состояния + функция которая по некоторому состоянию и входным данным выдает тройку - [новое состояние, выходное значение, Decision]
- Decision - решение о продолжении выполнения - закончить или продолжать; спустя какое-то время или сразу же
- Driver - по сути стейтфул итератор - менеджит состояние Schedule - выставляя наружу только входные данные => выходное значение

Решил создать упрощенную модель на промисах, чтобы понять, как работает.
interface Schedule<out Out, in In = unknown, in out S = any> {
readonly initial: S,
readonly step: (input: In, currentState: S) => [newState: S, Out, Decision],
}

type Decision = { tag: "Continue"; delay: 0 | number } | { tag: "Done" };

interface Driver<Out, in In, in out S> {
private state: S;
constructor(readonly schedule: Schedule<Out, In, S>)
next(input: In): Promise<Out>
}

Давайте например напишем Schedule который будет стрелять 5 раза с интервалами как последовательность фибоначчи 0-1-1-2-3 секунд. Определим сначала unfold - операцию обратную reduce - из одного значения мы будем получать последовательность.
const unfold = <A,>(initial: A, f: (a: A) => A): Schedule<A, unknown, A> =>
new Schedule<A, unknown, A>(initial, (_, state) => [
f(state), // новое состояние
state, // выходное значение
{ tag: "Continue", delay: 0 }, // мгновенное продолжение
]);

Теперь fibonacci - передаем начальное значение и функцию шага
const fibonacci = unfold<[number, number]>([0, 1], ([prev, cur]) => [cur,cur + prev])

давайте теперь напишем map чтобы создавать новый Scheduler с новым выходным значением — дергаем оригинальный и воздействуем на его выход
const map = <Out, Out1>(f: (out: Out) => Out1) =>
<In, S>(schedule: Schedule<Out, In, S>) =>
new Schedule<Out1, In, S>(schedule.initial, (input, state) => {
const [_state, _out, _decision] = schedule.step(input, state);
return [_state, f(_out), _decision];
});

Все остальные комбинаторы похожи структурой, допустим комбинатор intersect принимает два расписания и возвращает новый new Schedule в кишках которого выясняет, что вернут предыдущие и обрабатывает результат и возвращает свой - если какой-то Schedule решил остановиться то останавливается сам, если оба эмитят то эмитит наименьшее время.
Далее это передается в Driver который сохраняет состояние которое вернул Schedule и передает ему же на вход при следующей итерации, может сброситься до начального значения - потомучто эта информация зашита в сам Scheduler. Функция repeat в цикле запускает driver.next и запускает эффект, до тех пор пока не получит терминальную отмашку.

Tак как это просто _описание_ расписания - подменив Driver мы можем изменить поведение - давайте исполним все, например, игнорируя интервалы. или встроим в какой-нибудь rxjs?
Самая мощь - это Schedule - он композабелен. Да даже в этом примере видно как из простого unfold с различными маппингамми мы создали достаточно сложное расписание.

Люблю разбираться как устроены фичи под капотом - потомучто это знание не только углубляет понимание конкретной технологии, но и расширяет арсенал транслируемых навыков.
такой Schedule я бы не допетрил написать с нуля самостоятельно, a теперь понял смысл и оказалось, что это просто свистопляска вокруг одной функции
 declare function step(input: In, currentState: S): [newState: S, Out, Decision];

Песок с разбором и полным кодом
  • 🔥 4
More from @khraks_dev
  1. Sep 10, 2026Добавил еще PR с Context.Mixin, чтобы подмешивать к классу поведение Context.Service Тепер…
  2. Sep 10, 2026Post #56
  3. Sep 7, 2026Post #55
  4. Sep 3, 2026Post #54
  5. Sep 3, 2026Итак HKT на основе TypeLambda interface TypeLambda { readonly Arg1: unknown; readonly Arg2…
  6. Aug 30, 2026# HKT Дефункционализация В TypeScript нет настоящих Higher-Kinded Types (HKT). А они приме…
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 →