epicweb-dev / cachified

🤑 wrap virtually everything that can store by key to act as cache with ttl/max-age, stale-while-validate, parallel fetch protection and type-safety support

Repository from Github https://github.comepicweb-dev/cachifiedRepository from Github https://github.comepicweb-dev/cachified

How to deal with missing values with createBatch?

rosswarren opened this issue · comments

Hi, thanks for creating this very useful library!

In the following example:

import type { CacheEntry } from 'cachified';
import LRUCache from 'lru-cache';
import { cachified, createBatch } from 'cachified';

type Entry = any;
const lru = new LRUCache<string, CacheEntry<string>>({ max: 1000 });

function getEntries(ids: number[]): Promise<Entry[]> {
  const batch = createBatch(getFreshValues);

  return Promise.all(
    ids.map((id) =>
      cachified({
        key: `entry-${id}`,
        cache: lru,
        getFreshValue: batch.add(id),
      }),
    ),
  );
}

async function getFreshValues(idsThatAreNotInCache: number[]): Entry[] {
  const res = await fetch(
    `https://example.org/api?ids=${idsThatAreNotInCache.join(',')}`,
  );
  const data = await res.json();

  return data as Entry[];
}

Imagine a scenario where some of the IDs that were requested in the fetch request do not return any result and are therefore missing from the data array. How should we deal with these missing values? Should we add null to the array or perhaps undefined?

Thanks!

That's an interesting question! And I would like to take some time for discussion and to think about this before presenting a possible "goto" solution.


Here's whats coming to mind. (I don't have time to fully validate this right now, will do in the coming days)

Adding null or undefined as option for the Entry is definitely the way to go. But currently that would also store these values in cache and only revalidate them once their TTL is over. If that's what you intend you're good to go 👍

A scenario that is not possible right now (I think) would be when you want to NOT store the missing null in cache and have getFreshValues try to get them again asap.

I've added/updated two sections about this to the readme and added the functionality to fine-tune cache-metadata in batch requests in the latest release v3.2.0.

see Fine-tuning cache metadata based on fresh values (This was already possible but not well documented, though this does not use createBatch the concept will also apply there)

I've also updated Batch requesting values to show the new onValue callback.


With this in mind here's what I'd do.

  • First and foremost, yes you want to add null, undefined or similar to the array you're expecting from the Promise.all
  • If you want to cache the information that you received an empty values exactly the same as the non-empty values, you don't need anything else.

If you want to not cache empty values or cache them for a much shorter time do something like this:

const batch = createBatch((ids): Promise<(string | null)[]> => {
  /* ... */
});

const values: (string | null)[] = await Promise.all(
  ids.map((id) =>
    cachified({
      /* cache and other options... */
      key: `entry-${id}`,
      ttl: 60_000,
      getFreshValue: batch.add(id, ({ value, metadata }) => {
        if (value === null) {
          /* -1 disables caching, you could also just set to a shorter time */
          metadata.ttl = -1;
        }
      }),
    })
  )
);