Skip to content

Instantly share code, notes, and snippets.

@Mando75
Last active August 28, 2022 23:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Mando75/f6ec61b39a39ce72043df47e4cfc6d32 to your computer and use it in GitHub Desktop.
Save Mando75/f6ec61b39a39ce72043df47e4cfc6d32 to your computer and use it in GitHub Desktop.
Suspended Execution using generator example
type ValidatorGenerator = AsyncGenerator<Error, number>
type OperationGenerator = AsyncGenerator<ValidatorGenerator, { num: number }, number>
/**
* This function yields an Error if the input is invalid
* or returns the data if it is ok
* @param number
*/
function* validator(number: number): ValidatorGenerator {
if (number % 2 === 0) {
yield Error(`${number} is a multiple of 2`)
}
return number
}
/**
* An example operation that needs to validate some data prior to performing some action
*
* This validator can return either an Error or a number.
* By yielding the result of the validation, we allow the top-level
* error handler to stop execution if the result is an error, and only
* resume the operation if the data is valid.
*
* Our example operation will multiply any valid inputs by 5
* @param input
*/
async function* exampleOperation(input: number): OperationGenerator {
const validatedInput = yield validator(input)
return {num: validatedInput * 5 }
}
type Operation = typeof exampleOperation
/**
* Top level function to run our operation within the context of the suspension boundary.
*/
async function runner(operation: Operation, initialValue: number) {
// Initialize the operation
const result = exampleOperation(initialValue)
// Run it in context
return runContext(result, initialValue)
}
/**
* Iterates through an operation and recursively checks for
* validation errors. In this example, we return the error instead of providing
* the value to the operation, but theoretically this could be used to short circuit something like a
* request handler
* @param gen
* @param nextVal
*/
async function runContext(gen: OperationGenerator, nextVal: number): Promise<{ num: number } | Error> {
const step = await gen.next(nextVal)
// No validation, we can just return the value
if (step.done) {
return step.value
}
// Handle iterating through validation
const validationResult = await step.value.next()
// Valid input, continue execution of top-level iterator
if (validationResult.done) {
return runContext(gen, validationResult.value)
}
// invalid input, return an error
return validationResult.value
}
/**
* Our expected result should be that odd numbers are multiplied by 5
* and even numbers are replaced with error messages
*/
function exampleRunner() {
const values = [1, 2, 3, 4, 5, 6, 7, 8, 9]
return Promise.all(values.map((value) => runner(exampleRunner, value)))
}
exampleRunner().then(console.log)
@Mando75
Copy link
Author

Mando75 commented Aug 28, 2022

I'm working on a more reliable implementation of this using the Either monad. From there I might try to extract the actual generator comprehension into a separate package for easier pattern re-usage https://github.com/Mando75/either-suspend

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment