Skip to content

Instantly share code, notes, and snippets.

@rauhryan
Created January 20, 2026 14:27
Show Gist options
  • Select an option

  • Save rauhryan/5ef8c9477accfedf2e8e7b9bd16a8602 to your computer and use it in GitHub Desktop.

Select an option

Save rauhryan/5ef8c9477accfedf2e8e7b9bd16a8602 to your computer and use it in GitHub Desktop.
AsyncOrGenerator
import { call, useAbortSignal, type Operation } from 'effection'
// ============================================================================
// Types
// ============================================================================
/** Result type for form validators */
export type ValidatorResult =
| string
| undefined
| { fields: Record<string, string> }
/** Cancellation behavior for async operations */
export type CancellationMode = 'none' | 'replace'
/**
* A function that returns either a Promise or an Effection Operation.
* Receives params with an AbortSignal for cancellation support.
*/
export type AsyncOrGenerator<TParams, TResult> = (
params: TParams & { signal: AbortSignal }
) => Promise<TResult> | Operation<TResult>
// ============================================================================
// Type Guards
// ============================================================================
/** Check if function is a generator function */
export function isGeneratorFunction(fn: unknown): boolean {
return (
typeof fn === 'function' && fn.constructor?.name === 'GeneratorFunction'
)
}
/** Check if value is an Operation (has Symbol.iterator) */
export function isOperation(value: unknown): value is Operation<unknown> {
return (
value !== null && typeof value === 'object' && Symbol.iterator in value
)
}
// ============================================================================
// Operations
// ============================================================================
/**
* Runs a user-provided async or generator function within effection.
* Provides AbortSignal to both via useAbortSignal().
*
* @example
* ```ts
* // With async function
* yield* runWithSignal(
* async ({ value, signal }) => {
* const res = await fetch('/api', { signal })
* return res.json()
* },
* { value: formData }
* )
*
* // With generator function
* yield* runWithSignal(
* function* ({ value, signal }) {
* yield* sleep(300)
* return yield* call(() => fetch('/api', { signal }))
* },
* { value: formData }
* )
* ```
*/
export function* runWithSignal<TParams, TResult>(
fn: AsyncOrGenerator<TParams, TResult>,
params: TParams
): Operation<TResult> {
const signal = yield* useAbortSignal()
const paramsWithSignal = { ...params, signal }
const result = fn(paramsWithSignal)
// If it's a generator/operation, yield* it directly
if (isOperation(result)) {
return yield* result
}
// If it's a promise, wrap with call()
return yield* call(() => result as Promise<TResult>)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment