Lifecycle of methods

Last updated 4 months ago

You will learn what `pre...` and `post...` events are. Knowing this will help understand how plugins work and how to write them.

What are they and how do they work

Methods, available after creating an instance of cache: buildKey, getItem, getExtra, setExtra, addExtra, hasItem, setItem and removeItem, have certain lifecycle.

This is how it looks like from the inside (for any method):

{
// (1)
method(arg) {
// (2)
const preData = getPreData('methodName', { arg, cacheInstance: this });
// (3)
const result = adapter.method(preData.arg);
// (4)
const postData = getPostData('methodName', { arg: preData.arg, result, cacheInstance: preData.cacheInstance });
// (5)
return postData.result;
}
}

Here's what is happening:

  1. Given method is called with argument(s) passed to it.

  2. preData is being fetched, which stands for: get me data for pre lifecycle part of this method, using passed object. Under the hood stash-it walks through all event handlers registered for preMethodName event:

    1. calls those handlers one by one

    2. first handler is called with that object passed as an argument

    3. returned value form each event handler is being passed to the next one (loop)

    4. returned data from the last event handler is returned as preData

  3. Adapter's method is being called with arg returned by getPreData and returns its result.

  4. postData is being fetched, which stands for: get me data for post lifecycle part for this method, using passed object containing additionally the result returned from adapter. Under the hood stash-it walks through all event handlers registered for postMethodName event:

    1. calls those handlers one by one

    2. first handler is called with that object passed as an argument

    3. returned value from each event handler is being passed to the next one (loop)

    4. returned data from the last event handler is returned as postData

  5. result that came back from adapter and was passed through getPostData is being returned finally by this method. Bare in mind that result might be different after getting through all event handlers.

When you look at it, you most probably think middlewares - yes, it works in similar fashion.

Now, let's use getItem method and inspect this in more detail:

// this is an instance of cache
{
// (1)
getItem(key) {
// (2)
const preData = getPreData('getItem', { cacheInstance: this, key });
// (3)
const item = adapter.getItem(this.buildKey(preData.key));
// (4)
const postData = getPostData('getItem', { cacheInstance: preData.cacheInstance, key: preData.key, item });
// (5)
return postData.item;
}
}
  1. cache instance's getItem(key) method is called, takes a key as an argument (depending on method used, there can be more arguments e.g. setItem(key, value, [extra])).

  2. preData is fetched. There are few things to remember, as there is a pattern here:

    • all methods (buildKey, getItem, setItem, ... ones mentioned at the top of this page) have event name passed as a string of value equal to that method's name. So, for example hasItem(key) method will have 'hasItem' string passed, removeItem(key) will have 'removeItem' passed, and so on;

    • getPreData will construct a full event name - here it will be 'preGetItem' (camelCase'd); so, for hasItem(key) it will be 'preHasItem' and so on;

    • object being passed contains properties for all arguments used in function called, here key only (for setItem(key, value, [extra]) it will contain key, value and extra)

    • that object will always contain cacheInstance which is a reference to this which is cache instance itself; this is useful if any event handler will need to use any cache instance's methods - be careful with that, as it gives you much power, so be responsible;

    Returned preData (an object) will contain the very same properties (as ones passed initially to getPreData), but values for those properties might be different.

    Why?

    If there are hooks added for given event (here 'preGetItem') and handlers (for those events) mutate the data (e.g. add prefix for the key or something else), then that mutated data is returned. If there are no hooks added for that event, data is returned without any changes.

  3. Adapter's method is being called, fetching item from storage. There are few things to mention here:

    • for all methods (except for buildKey) key is being built using cache's buildKey method (hence this.buildKey(preData.key),

    • key is being built using key value returned in preData, not key passed to getItem method.

  4. postData is fetched. There are few things to remember, as there is a pattern here as well:

    • all methods (ones mentioned at the beginning) have event name passed as a string of value equal to that method's name. So, for example hasItem(key) method will have 'hasItem' string passed, or removeItem(key) will have 'removeItem' passed;

    • getPostData will construct a full event name - here it will be 'postGetItem' (camelCase'd); so, for hasItem(key) it will be 'postHasItem' and so on;

    • object being passed contains properties for all arguments used in function called, here key only (for setItem(key, value, [extra]) it will contain key, value and extra), but also item returned by adapter;

    • that object will always contain cacheInstance which is a reference to this which is cache instance itself; again, this is useful if any event handler will need to use any cache instance's methods;

    Returned postData (an object) will contain the very same properties (as ones passed initially to getPostData), but values for those properties might be different.

    Why?

    Similar to preGetItem. If there are hooks added for given event (here 'postGetItem') and handlers (for those events) mutate the data (e.g. increment counter how many times item was fetched from cache), then that mutated data is returned. If there are no hooks added for that event, data is returned without any changes.

    So, here, for getItem method postData will have key, item and cacheInstance property. There can be additional properties added (by mentioned handlers), but those three will be there always.

  5. Finally, item is returned, the one stored in preData, not the one returned directly by adapter.

And that's it.

Each method has its lifecycle described and all arguments that are passed by are mentioned as well.