Skip to content

Instantly share code, notes, and snippets.

@fuzzypawzz
Last active December 3, 2022 15:08
Show Gist options
  • Save fuzzypawzz/b8ab885e502abee7a87d5b5a05a82a71 to your computer and use it in GitHub Desktop.
Save fuzzypawzz/b8ab885e502abee7a87d5b5a05a82a71 to your computer and use it in GitHub Desktop.
TypeScript Promise cache
import { v4 as uuidv4 } from 'uuid'
type CachedPromise = Promise<any>
type PromiseDetails = Record<string, unknown>
type PromiseId = string
type MatcherCallback = (args: PromiseDetails) => boolean
type ActionId = string
interface IPromiseDataModel {
id: PromiseId
promise: CachedPromise
details: PromiseDetails
}
type PromiseStorage = Record<string, IPromiseDataModel[]>
interface IRequestDetails {
actionId: ActionId
action: () => CachedPromise
requestDetails: PromiseDetails
matcher: MatcherCallback
}
type CreateCachedRequest = <PromiseReturnType>(
args: IRequestDetails
) => Promise<PromiseReturnType>
enum ERROR_MESSAGE {
CALLBACK_REQUIRED = 'Matcher callback is required.',
DATA_MODEL_MISSING = 'Cached promise model does not exist for actionId:',
}
/**
* Only allowed actions can create a cache.
* These are unique action identifiers here.
*/
const actionIdentifiers = [
'SOME_ACTION_ID'
]
const createPromiseStorage = (): PromiseStorage =>
actionIdentifiers.reduce(
(result, actionId) => Object.assign(result, { [actionId]: [] }),
{}
)
/**
* PROMISE CACHING HELPER
*
* @method promiseCache Closure
*
* @description Use this helper to prevent calling an async method several times while there is an active
* unfulfilled promise. You can use it to prevent spamming a specific endpoint if multiple
* components are calling a method to fetch some data.
* The unfulfilled promise will be returned instead of making a new call to the method.
* The cached promise will be removed from the cache as soon as it's resolved or rejected.
*
* @returns A method that when called creates a cache for the promise and returns the cached promise.
*/
export const promiseCache = ((): CreateCachedRequest => {
const storage = createPromiseStorage()
const removePromise = (model: IPromiseDataModel[], id: PromiseId): void => {
const index = model.findIndex((model) => model.id === id)
if (index !== -1) model.splice(index, 1)
}
return ({ actionId, requestDetails, matcher, action }) => {
if (!matcher) throw new Error(ERROR_MESSAGE.CALLBACK_REQUIRED)
const promiseDataModel = storage[actionId]
if (!promiseDataModel)
throw new Error(`${ERROR_MESSAGE.DATA_MODEL_MISSING} ${actionId}`)
const existingRequest = promiseDataModel.find(({ details }) =>
matcher(details)
)
if (existingRequest) return existingRequest.promise
const id = uuidv4()
const request: IPromiseDataModel = {
id,
details: requestDetails,
promise: action().finally(() => removePromise(promiseDataModel, id)),
}
promiseDataModel.push(request)
return request.promise
}
})()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment