Skip to main content

Memoization

Yggdrasil Server includes a memoization utility that can help you memoize expensive function calls you may make while handling requests.

Results from memoization are stored in the ygg context, so each memoized task will run at most once per request.

Usage

To memoize a function, simply use ygg.memoize:

src/utilities.ts
// Before memoization:
export async function myExpensiveUtility() {
return await doExpensiveTask(); // Some arbitrary expensive task
}

// After memoization:
export async function myExpensiveUtility(ygg: YggdrasilContext<AppType>) {
return ygg.memoize("myExpensiveUtility", async () => {
return await doExpensiveTask();
});
}

Now, to call the function:

src/routes/memo.ts
import { DemoServer } from "../server";

export const memo = DemoServer.defineRoute("GET", "/memo", async (ygg) => {
// We call myExpensiveUtility twice, but memoization means that
// the underlying operation is only performed once. When the operation
// finishes, both promises resolve with the same result. Subsequent
// calls immediately return the result.
const results = Promise.all([
myExpensiveUtility(ygg),
myExpensiveUtility(ygg)
]);

return new Response("Expensive operation results: " + results.join(","), { status: 200 });
});

Memoization techniques

When memoizing a function, please consider the following:

  1. Memoizing a function requires making it take the ygg context as a parameter so that the result can be associated with the request.
  2. Yggdrasil Server's memoization utility stores and identifies the request and response based on a key string. In the above example, this is simply "myExpensiveUtility". However, if you want to memoize a function with parameters, a more complex scheme for creating a key will be required. For example, you could do "myExpensiveUtility,23" if your utility takes a number and is given 23. Any scheme works, as long as the key is unique for the request/response pair to memoize.

Storing and restoring results

In some cases, you may want to store and restore results between request. For example, you may have a retry mechanism that generates a copy of the initial request upon failure. In this case, you may want to avoid re-doing any successful memoized tasks from the first request. Yggdrasil Server supports this using the following API functions:

src/index.ts
// Handling the initial request:
DemoServer.handleRequest(initialRequest, async (ygg) => {
try {
return await handleRequest(ygg);
} catch(e) {
// Store the memoized results so we can use it later
await storeMemoizedResults(ygg.getMemoizedResults());
}
}, {
ctx
});

// Handling the retry request:
DemoServer.handleRequest(retryRequest, async (ygg) => {
return await handleRequest(ygg);
}, {
ctx,
// Initialize the ygg context with the results we already have
memoizedResults: await getMemoizedResults()
});

It is important to emphasize that this should only be done in the same short-lived context (e.g. one request/operation). Sharing results in a larger scope can lead to data leaks (a security issue) or stale results (a correctness issue).