Feature request - function node context store getter which sets default value

Hi,

I think it would be fantastically useful to extend the context store getter such that in a function node I could specify a default value when calling context/flow/global.get().

If the key is not found then the getter would create the default value/object and return that value or reference to that newly created object (ie a type of atomic get-and-set-if-not-exist operation).

I’m thinking that this would be particularly useful in the case of the value being an object. This way I’d be guaranteed to receive a reference to an object which I could use/update without needing to write a bunch of defensive boilerplate.

For instance (trivial example), on startup the flow context store is empty. I can then simply call the get and on first iteration create a default value & receive a reference to that default:

const timestampsArray = flow.getWithDefault(“timestamps”, [])
const newTimestamp = Date.now()
timestampsArray.push(newTimestamp)

…aaaand job done. Context store now contains:

{timestamps: [1704084912228]}

On second iteration I get the reference to the now previously created timestamps object and the context store now contains:

{timestamps: [1704084912228, 1704085334560]}

Very convenient & no need for defensive boilerplate, redundant setter calls, etc etc.

Wouldn’t mind if it’s a new method or simply extension of the existing .get() method.

What do you think?

I believe this covers your needs with minimal code

//Get flow.timestamps else create timestamp
var context_flow_timestampsArray = flow.get('timestamps') || [];
const newTimestamp = Date.now()
context_flow_timestampsArray.push(newTimestamp)

flow.set('timestamps', context_flow_timestampsArray);


return msg;

this works for objects. ---> var context_flow_timestampsObjects = flow.get('timestamps') || {};

There are times when a default value would be useful. Consider the case where the value in context is a simple integer which needs to be defaulted to -1, but where 0 is a valid value. Then that technique does not work.

Thanks for the suggestion, however it misses the point.

I currently use the kind of defensive boilerplate that you’re suggesting - and that’s exactly what I’m trying to eliminate.

Not only can it get rather convoluted if the valid default case is falsey - but in the case of objects (and arrays being a type of object) your suggested code unnecessarily invokes the setter function every single time after the initial run.

What I’m asking for allows for clean, efficient, readable, and easily understood code.

Which setter function is (additionally) invoked when using || rather than a (potential) getWithDefault?

That's exactly the use-case why the Nullish coalescing operator (??) had been introduced. Thus a clean and short way could be:

const timestampsArray = flow.get(“timestamps”) ?? [];

1 Like

There's an alternative already implemented in the codebase:

const timestampsArray = flow.get(“timestamps”, function(err, tA) {
    return tA ?? [];
})

This even allows you to fetch & process several contexts with one run:

flow.get([“timestamps”, "another"], function(err, tA, anth) {
    [...]
});

Details here...

flow.set('timestamps', context_flow_timestampsArray);

This is redundant when receiving an object reference.

Now I - think I - get your point: The idea is to run an implicit flow.set in the background if the context isn't defined already.

Precisely. I ask for a value by key & provide a default value. If the key doesn’t exist then create the kvp and return either the default scalar value or the reference to the newly created default object (ie as if that object already existed in the data store).

What a lot of people don’t seem to understand is that if the context store kvp value exists & is an object then context/flow/global.get() returns a reference to that object.

In this case manipulating what is returned directly affects the object in the context store. A subsequent .set() operation is entirely redundant.

Re the sample you provided -

const timestampsArray = flow.get(“timestamps”, function(err, tA) {
    return tA ?? [];
})

I don’t think this will actually work because:
a) this signature is only valid for async data stores and hence doesn’t return values (instead, simply invoking function as a callback) - though I’m not 100% certain as I don’t have a chance to investigate for a few days, and
b) probably more important - if a) was incorrect & it did indeed operate as you expect - whilst it returns the default value that default value is not persisted, so every invocation would simply yield the default value

Anyways - thanks for the suggestions!

That is only true for in memory context store. If you ever changed over to a persistent store then the set is required. It is recommended to always do a set even with objects.

1 Like

I also noticed this behavior and it is true that for a novice there is a risk of accidentally modifying their object. It may be necessary to deep clone the object?

You're right w/ a). One needed to get the values out of the callback function:

let timestampsArray;
flow.get(“timestamps”, function(err, tA) {
    timestampsArray = tA ?? [];
})

This yet isn't the focus of your request - as I understood meanwhile.

This topic was automatically closed 60 days after the last reply. New replies are no longer allowed.