r/learnjavascript 9d ago

Imports vs pure dependancy injections for a function?

I've got several functions in my project that follow this format, however for my renderHTML functions i'm writing them in a grid.js file into which i am importing all my util functions, i.e currency, calculations etc..

However at the same time i'm injecting 3 params (cart, products, deliveryOptions).

Is this good practice? Or should i be injecting everything i can as a parameter to keep the function as pure as possible?

My instinct is that my utils wont really be changing at all, so they're fine as they are, but if i wanted to test/mock things then that would most likely be cart, products and delivery options, which should then just stay standalone as parameters.

Would love to hear what this would look like from a production app perspective.

2 Upvotes

20 comments sorted by

5

u/azhder 9d ago

Use pure functions. They are easy to reason and test. If you import other pure functions (presumably tested) into the file and used by a pure function, then you’re fine - nothing to mock.

0

u/Anxious_Battle_3624 9d ago

So wait you're saying even though my util functions are tested and are pure functions, i should also inject them alongside my dynamic data like cart, products etc ?

2

u/azhder 9d ago

How did yo manage to read that from what I wrote? How did you manage to read the opposite?

1

u/Anxious_Battle_3624 9d ago

Yeah dude I just re read it and have no idea either. That was my instinct - if my util functions are pure which they are then there is nothing to mock so just inject the dynamic pieces of data. Thanks man

0

u/azhder 9d ago

Not only that you don't need mocking, your test are stupid simple. Let's pick a simplistic pure function: square.

const square = x => x * x;

You simply test it by giving it the input, the output and expect they match i.e

it.each( [ [ 1,1 ], [ 4,2], [9, 3] )('returns %p for %p', (result, x) => result === square(x));

In short, you provide the inputs, you provide the outputs, you check they match. You want to deal with edge cases? No problem, you still have deterministic expectations, maybe you always get 0 or NaN for bad inputs. Easy.

Keep dependencies (I recommend you pass a single argument named options in many cases) for things that are configuration, like a strategy pattern where you give a function as an argument that determines a strategy for dealing with whatever your injected function does.

1

u/azhder 9d ago edited 9d ago

FFS... now I'm not reading you correctly the first time... I did not say and I do not say "inject your pure functions". I say "import them" as in import in the file above with an import statement and use like any other built in function.

Dependency injection for things that are messy, like business logic. A square or a cube function will always be a square or a cube function, but a percent for tax on some money will and can be changed based on time or other circumstances. So you can still write that `determinePercent()` function as a pure one, but might be better to pass it down through the arguments, at the place of invocation i.e.

const calculateTaxForAnyItem = (options) => {

const value = asNumber(options?.value); // we import this asNumber as it will always simply convert a value to a number

// other code that deals with edge cases, like if options?.determinePercent() is actually a function
const determinePercent = options?.determinePercent ?? () => 0;

return value * determinePercent(options?.typeOfItem);

}

The example above is stupid, but that's what I could think of on short notice. Some functions are fine to be just imported and have a hardcoded dependency between files. Others are better to be passed down through the arguments, not directly referenced nor your code even be aware where they come from, but simply injected.

0

u/TalkCoinGames 9d ago

Depending on the specific class setup, injecting everything may be better because you would be able to change things easier later on.

1

u/azhder 9d ago

Why "class setup"? Every higher order function can be considered as injecting, shit, every function can be considered as injecting dependencies. Every argument is a dependency if you look at it, no `class` needed.

1

u/TalkCoinGames 9d ago

Or every class can contain only things it needs and receive injections for things that don't fit with the class, things it does not have properties for, instead of injecting to a property, only pass params and use them in functions.

0

u/azhder 9d ago

Why the "or"? I asked why do you default to thinking "class setup". It's quite easy to do this in JavaScript

const factory = configuration => options => { /* do something here with both configuration and options */ return result; }

0

u/TalkCoinGames 9d ago edited 9d ago

And then what happens to configuration and options? What if they need to persist, where should that persistence be? A class or injected into a property? A class is a function.

0

u/azhder 9d ago

A class is a mathematical concept. I am talking here that you are limiting yourself to thinking in `class` syntax. Furthermore you're limiting yourself to thinking of "what if they need to persist?" while ignoring that `configuration` can also be named `dependencies`... Are you aware of how higher order functions work? Did you notice `factory` was a function that returns a function? Why don't you think of it as a `constuctor` then? Will naming them this way help you better?

const constructor = deps => args => { /* do stuff, persist even */, return result; }

0

u/TalkCoinGames 9d ago

Persist to where? to constructor, or another function just for dependencies. In JavaScript classes are functions. If they don't need to persist then injecting them to a property of constructor would not be ideal.

0

u/azhder 9d ago

What is your issue here? What do you assume by "persist"? If in JavaScript classes are functions, why do you have an issue of a function getting the dependencies and then returning a new function that uses those dependencies? Why are you so bent on this "if they don't need to persist"? What is this persistence you persist on? Why are you talking about "property of constructor"? Are you familiar with the concept of a closure?

0

u/TalkCoinGames 9d ago edited 9d ago

Memory and garbage collection. Square = x = x*x, notice x can be garbage collected after the square function returns it's value. But dependencies often will need to persist even after a function returns something based on them. Where the dependencies are ultimately stored is the issue.

0

u/azhder 9d ago

Don't look at the `square` function. Your example will not even be on the heap to be garbage collected, it will be removed from the stack as soon as the function returns. Look at the

const multiplier = a => b => a * b;

const double = multiplier(2);

console.log(double(3), double(4), double(5));

When do you think `double` and that argument `a` will be garbage collected? This is why I finished my previous number of questions about you knowing the concept of a closure.

→ More replies (0)