receta
Memo

Error Handling

Managing errors in memoized functions, especially async operations.

Failed Promises Are Not Cached

import { memoizeAsync } from '@ontologyio/receta/memo'

let callCount = 0

const flaky = memoizeAsync(async (id: string) => {
  callCount++
  if (callCount === 1) {
    throw new Error('First call fails')
  }
  return `Success: ${id}`
})

// First call fails
await flaky('123').catch(e => console.log(e.message))
// Console: "First call fails"

// Second call retries (error was not cached)
const result = await flaky('123')
// Returns: "Success: 123"

Key Behavior: Failed promises are automatically removed from cache, allowing retries.

Cache Invalidation on Error

const fetchData = memoizeAsync(async (id: string) => {
  const res = await fetch(`/api/data/${id}`)
  if (!res.ok) throw new Error(`HTTP ${res.status}`)
  return res.json()
})

// Network error
await fetchData('123').catch(e => {
  console.log('Failed:', e.message)
  // Cache entry for '123' is automatically removed
})

// Retry immediately
await fetchData('123') // Will retry the fetch

Manual Cache Invalidation

Single Entry Invalidation

import { memoizeAsync, clearCache } from '@ontologyio/receta/memo'

const getUser = memoizeAsync(async (id: string) => {
  return await db.users.findById(id)
})

// When data changes
async function updateUser(id: string, data: Partial<User>) {
  await db.users.update(id, data)

  // Invalidate specific entry
  getUser.cache.delete(id)
}

Batch Invalidation

import { memoizeAsync, invalidateMany } from '@ontologyio/receta/memo'

const getUser = memoizeAsync(fetchUser)

// After batch update
async function updateUsers(ids: string[], data: Partial<User>) {
  await db.users.updateMany(ids, data)

  // Invalidate multiple entries at once
  invalidateMany(getUser, ids)
}

Conditional Invalidation

import { memoize, invalidateWhere } from '@ontologyio/receta/memo'

const getUser = memoize(fetchUser)

// Invalidate all admin users after permission change
function revokeAdminAccess() {
  invalidateWhere<string, User>(getUser, (_key, user) => user?.role === 'admin')
}

// Invalidate by key pattern
function clearDraftPosts() {
  invalidateWhere(getPost, (key: string) => key.startsWith('draft:'))
}

invalidateWhere needs a cache it can walk through, so use it with the default cache, ttlCache, lruCache or a custom cache that has a forEach method. It throws for weakCache, because a WeakMap can't be listed. With memoizeAsync, the value it passes is the cached promise.

Multi-Cache Invalidation

import { memoizeAsync, invalidateAll } from '@ontologyio/receta/memo'

const getUser = memoizeAsync(fetchUser)
const getUserPosts = memoizeAsync(fetchUserPosts)
const getUserComments = memoizeAsync(fetchUserComments)

// Delete user and clear all related caches
async function deleteUser(id: string) {
  await db.users.delete(id)

  // Clear all user-related caches at once
  invalidateAll(getUser, getUserPosts, getUserComments)
}

Full Cache Clear

import { clearCache } from '@ontologyio/receta/memo'

const getUser = memoize(fetchUser)

// Clear entire cache
clearCache(getUser)
// or
getUser.clear()

Invalidation Strategies

StrategyFunctionUse Case
Single entrycache.delete(key)Update one item
Multiple entriesinvalidateMany(fn, keys)Batch updates
Pattern-basedinvalidateWhere(fn, predicate)Conditional logic
Multi-cacheinvalidateAll(...fns)Related caches
Full clearclearCache(fn) or fn.clear()Reset everything

Next Steps

On this page