npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

lilypads

v1.3.2

Published

Memoized functions done right

Downloads

23

Readme

lilypads

memoized functions done right

npm install lilypads

Why, and what it does

I found myself writing a lot of optimised handler functions that were repeating a lot of the optimization techniques over and over again. This module does all of that for you.

Provided a unique id, lilypads will, after the first call, ensure immediate function responses.

Upon an initial first call with the id user-34134-data, it will get the data from the provided responder function. However, next time the same request is made, lilypads will naively and immediately send the same result as it got from the responder function earlier.

In addition to this, lilypads has @amphibian/party built in, which ensures multiple calls to what would give the same response only trigger the responder function once.

Usage

import lilypads from 'lilypads';
import {slowGetUserData} from './interfaces/user';

export default function optimizedGetUserData(userId) {
    return lilypads({
        id: `optimizedGetUserData/${userId}`,
        lifetime: 5 * 60 * 1000 // 5 minutes
    }, () => getUserData(userId));
}

Step by step

Assume a call to id user-34134-data:

First call

  1. Calls the responder function.
  2. Returns the response.

Second call

Immediately returns the previous result of the responder function.

Fourth call, assuming provided lifetime has expired

  1. Immediately returns the previous result.
  2. In the background, calls the responder function and swaps the current result with a new one.

A note on error handling

If the lilypads responder encounters an error the first time it runs, it will throw an error. However, if it has already been run successfully, lilypads will swallow the error and send it to the optional errorHandler you can provide.

Consider the following code:

let shouldError = false;

function slowGetUserData(userId) {
    if (shouldError) {
        throw new Error();
    }

    shouldError = true;
    return {user: 'test'};
}

function optimizedGetUserData(userId) {
    return lilypads(context, {
		id: `optimizedGetUserData/${userId}`
	}, () => getUserData(userId));
}

(async () => {
    await optimizedGetUserData('1');
    await optimizedGetUserData('1');
})();

No errors will be thrown because the responder function has already had a successful run. The error can be handled by implementing an errorHandler:

// ...

await lilypads(context, {
	id: `optimizedGetUserData/${userId}`
}, () => (
	getUserData(userId)
), (error) => {
    console.error('This error happened:', error);
});

// ...

However, if the error is thrown before the responder has been run once, successfully, the error is thrown “as normal”:

// ...

try {
    await lilypads(context, {
		id: `optimizedGetUserData/${userId}`
	}, () => (
		getUserData(userId)
	), (error) => {
		console.error('This error happened:', error);
	});
} catch (error) {
    console.error('This error happened:', error);
}

// ...

To ensure an error is always thrown, use lilypads.ForceThrowError:

let shouldError = false;

function slowGetUserData(userId) {
    if (shouldError) {
        throw new lilypads.ForceThrowError();
    }

    shouldError = true;
    return {user: 'test'};
}

function optimizedGetUserData(userId) {
    return lilypads(context, {
		id: `optimizedGetUserData/${userId}`
	}, () => getUserData(userId));
}

(async () => {
    await optimizedGetUserData('1');
    await optimizedGetUserData('1');
})();

This time, an error will be thrown, even if the previous responder function had a successful run. Both of these approaches will work:

throw new lilypads.ForceThrowError();
throw new lilypads.ForceThrowError(new Error('my error'));

A note on cache invalidation

Sometimes you make changes in your, ie., database that you would like to reflect immediately. There's an option to force update a lilypad in the options object: forceUpdate.

It should be set to either sync or async depending on the desired effect. If you make a change that does not need immediate reflection, use async. If not, use sync.

// ...

function getUser(userId, options) {
    return lilypads({
        ...options,
        id: `getUser/${userId}`
    }, () => getUserDataFromDatabase(userId));
}

function updateUser(userId) {
    await updateUserInDatabase(userId, {email: '[email protected]'});
    return getUser(userId, {forceUpdate: 'sync'});
}

// ...

forceUpdate should only be set on the lilypad call when you know there's been a change. You could also implement some invalidation logic to be evaluated on runtime:

// ...
import invalidate, {stale} from '../my-utilities/invalidations';

function getUser(userId, options) {
	if (stale(`my-invalidation-logic/${userId}`)) {
		options.forceUpdate = 'sync';
	}

    return lilypads({
        ...options,
        id: `getUser/${userId}`
    }, () => getUserDataFromDatabase(userId));
}

async function updateUser(userId) {
	await updateUserInDatabase(userId, {email: '[email protected]'});
	invalidate(`my-invalidation-logic/${userId}`);
    return getUser(userId);
}

// ...

lilypads

Usage

lilypads(options, responder);
options (Object) Required.
options.id (String) Required.

Should be unique, yet the same for requests that expect the same response. Function arguments used within responder should probably be represented here in some way. For example:

  • user/34134
  • my-blog/article/213
options.lifetime (Number)

How long each responder result will live in milliseconds. If undefined, the result lives forever (or until forceUpdate is set). If set to, eg., 3000, leap will get a new version after 3000ms. But it won't throw out the old one until the new one is ready.

options.forceUpdate (String): sync|async

To force update the lilypad, set forceUpdate to either sync or async. This will ensure the responder function is called to update the cached return value.

You have two choices:

sync

The lilypad will call the responder function and resolve upon its completion. This is useful when the change made needs to be reflected immediately.

async

The lilypad will resolve immediately, as normal, returning an “old” responder result (if any) – but will, in the background, call the responder function to update the lilypad.

responder (Function)

The function that returns the request response. It is given no arguments when called. Can return a Promise.

errorHandler (Function)

The function that is given any error encountered running the responder function.

Returns lilypad

The response.