Effect и избавляет от проблемы раскраски функций. Все же может быть полезно отслеживать асинхронное выполнение, допустим, чтобы в решающий момент быть уверенным, что Effect.runSync не выкинет ошибку.Для этого можно использовать такой прием - давайте помечать
Effect меткой на тайп-левеле, а runSync ограничим таким образом, чтобы вызов эффекта с такой меткой был невалидным.const AsyncTypeId: unique symbol = Symbol.for("my-project/Async");
type AsyncTypeId = typeof AsyncTypeId;
interface Async {
[AsyncTypeId]: AsyncTypeId;
}
function markAsync<A, E, R>(
effect: Effect.Effect<A, E, R>,
): Effect.Effect<A, E, R> & Async {
return effect as any;
}
function runSync<A, E, Eff extends Effect.Effect<A, E>>(
effect: Eff extends Async ? never : Eff,
) {
return Effect.runSync(effect);
}
В этом примере использовали брендирование
& Async — но это наивный подход, потомучто он не будет работать с большинством встроенных функций Effect, так как они имеют сигнатуру не сохраняющую подтип самого эффекта, а оперируют только над его содержимым:declare const map: {
<A, B>(f: (a: A) => B): <E, R>(self: Effect<A, E, R>) => Effect<B, E, R>
}
Более интересным решением будет использовать R-канал для наших целей —
Effect<A, E, R | Async>. С такой сигнатурой не нужно оборачивать runSync, потомучто он требует Effect<A, E, never>. И можно ввернуть кастомный асинхронный раннер:function runMarkedAsync<A, E, R>(effect:
[Exclude<R, Async>] extends [never]
? Effect.Effect<A, E, R>
: Effect.Effect<A, E>
) { /* ... */ }
Мораль такова:
если нужно пометить воркфлоу каким-то образом, то стоит использовать
R-канал, а не брендированиеTS-песочница с примером использования: https://tsplay.dev/m0kYaN