Last active
December 22, 2023 11:16
-
-
Save denk0403/1f17d0bc9278fd70ee1d18b202b6b8fc to your computer and use it in GitHub Desktop.
SyncContext and AsyncContext with Generators
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* A type representing any function. | |
*/ | |
type AnyFunction = (...args: Array<any>) => any; | |
/** | |
* A type representing any generator function. | |
*/ | |
type GenFn<T = unknown, TReturn = any, TNext = unknown> = ( | |
...args: Array<any> | |
) => Generator<T, TReturn, TNext>; | |
/** | |
* A type representing the return type of a generator. | |
*/ | |
type GeneratorReturnType<T extends Generator> = T extends Generator< | |
unknown, | |
infer R, | |
unknown | |
> | |
? R | |
: never; | |
/** | |
* A utility for synchronous context propagation. | |
* | |
* This class is **NOT** safe to use with async/await callbacks. | |
*/ | |
class SyncContextWithDefault<T> { | |
private _values: Array<T>; | |
/** | |
* The current context value. | |
* @returns The current context value. | |
*/ | |
public get value(): T { | |
return this._values[this._values.length - 1]; | |
} | |
/** | |
* Creates a new `SyncContextWithDefault` with the provided default value. | |
* @param defaultValue - The default context value. | |
*/ | |
public constructor(defaultValue: T) { | |
this._values = [defaultValue]; | |
} | |
/** | |
* Invokes the provided callback with the following | |
* arguments and the provided value as the new context | |
* value for the duration of its execution. | |
* @param value - The new context value. | |
* @param callback - The callback to invoke. | |
* @param args - The arguments to supply to the callback. | |
* @returns The result of the callback. | |
* @example | |
* userContext.run(currentUser, () => { | |
* return UserService.getCurrentUserFriends() // accesses userContext.value | |
* }); | |
*/ | |
public run<F extends AnyFunction>( | |
value: T, | |
callback: F, | |
...args: Parameters<F> | |
): ReturnType<F> { | |
try { | |
this._values.push(value); | |
return callback(...args); | |
} finally { | |
this._values.pop(); | |
} | |
} | |
/** | |
* Runs a generator with the provided value as the new | |
* context value for the duration of its execution. | |
* | |
* **NOTE:** The context value can **only** be accessed from | |
* within synchronous functions or other synchronous generators. | |
* | |
* @param value - The new context value. | |
* @param genFn - A function which produces a generator. | |
* @param args - The arguments to supply to the generator function. | |
* @returns The result of the generator. | |
* @example | |
* userContext.runGen(currentUser, function* () { | |
* const friends = yield UserService.getFriends(currentUser) // async | |
* const relatives = getCurrentUserRelativesFromFriends(friends); // accesses userContext.value | |
* yield BirthdayService.sendBirthdayCards(relatives); // async | |
* return createBirthdayCardSentResponse(relatives); // accesses userContext.value | |
* }); | |
* | |
* userContext.runGen(currentUser, UserService.getFriendsOfType, 'relative'); | |
*/ | |
public async runGen<U, F extends GenFn<U, unknown, Awaited<U>>>( | |
value: T, | |
genFn: F, | |
...args: Parameters<F> | |
): Promise<GeneratorReturnType<ReturnType<F>>> { | |
const gen = genFn(...args); | |
let currentIteration = gen.next(); | |
while (!currentIteration.done) { | |
const asyncResult = await currentIteration.value; | |
this.run(value, () => { | |
currentIteration = gen.next(asyncResult); | |
}); | |
} | |
return currentIteration.value; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment