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
:
// 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:
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:
- Memoizing a function requires making it take the
ygg
context as a parameter so that the result can be associated with the request. - 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 given23
. 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:
// 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).