Svelte: Multiple or single lifecycle hook implementation preferred? - hook

When having to deal with multiple functions to invoke inside a svelte lifecycle hook, is there a preferred way to accomplish this?
Should all calls be put inside a single anonymous function, or should multiple lifecycle hooks be registered?
For example:
function func1() { ... }
function func2() { ... }
// Like this
onMount(() => {
func1()
func2()
})
// Or like this?
onMount(func1)
onMount(func2)
// Or even differently?
Is this just based on what fits better to the function content and does not matter that much in general, or is there a performance impact choosing on over the other (or any other reason)?

In terms of performance this probably does not matter unless you have thousands of instances.
Otherwise it is primarily a matter of organization and dependencies. It may be more convenient to have separate hooks if they have a return function which is called on destroy. When using a single hook, those returned functions have to aggregated instead.
E.g.
onMount(() => {
const cleanup1 = func1();
const cleanup2 = func2();
return () => {
cleanup1();
cleanup2();
}
});
Vs. simply
onMount(func1);
onMount(func2);

Related

Jest mocking private members

In a Node/Express server, we use a repository that needs to be unit-tested using Jest.
//Private things
let products;
function loadProducts() {
if (!products)
products = fetchProductsFromSomeDbOrServiceOrWhatever()
}
function saveProducts() {
persistPrivateProductsToADbOrServiceOrWhatever()
}
// Exported/public things
export function read() {
loadProducts();
return products;
}
export function add(product) {
loadProducts();
products.push(product);
saveProducts();
}
We want to unit test like this:
import { read, add } from './productRepo';
it('can read products', () => {
expect(read().length).toBe(5);
});
it('can add a product', () => {
const oldNum = read().length;
add({id:0, name:'test prod', moreProps});
expect(read().length).toBe(oldNum+1)
});
You get the idea. It's not a class so we can't mess with the prototype.
Problem: How do I mock the private products and/or loadProducts and/or saveProducts so that it isn't reading from the actual data source?
Presumably these private functions call out to other pieces of functionality you've written yourself or imported from libraries.
function loadProducts() {
if (!products)
products = fetchProductsFromSomeDbOrServiceOrWhatever()
}
function saveProducts() {
persistPrivateProductsToADbOrServiceOrWhatever()
}
Let's take fetchProductsFromSomeDbOrServiceOrWhatever as the example. One basic architectural consideration to make the code properly encapsulated and testable is to put this functionality in a separate module. So I would expect an import at the head of the file:
import fetchProductsFromSomeDbOrServiceOrWhatever from './fetchProductsFromSomeDbOrServiceOrWhatever'
So in this case just mock it in your test file:
jest.mock('./fetchProductsFromSomeDbOrServiceOrWhatever');
If the functionality is not extracted into a separate module this makes your code less testable; this on its own is a good reason to refactor.
Note: the other replies on this thread are correct when they say that private functions of classes should not be tested, but I think that is a slightly different issue from the one you are asking.
First, start initializing products to an empty array, else tests are doomed to fail because of the null value. Also change the null check
Then parametrize your loader and saver functions so your functions can be testable. Last write tests for you loader and saver functions outside of this repo function.
// assummed imports
fetchProductsFromSomeDbOrServiceOrWhatever=()=>{}
persistPrivateProductsToADbOrServiceOrWhatever=()=>{}
//Private things
let products=[];
function loadProducts(loader) {
loader=loader || fetchProductsFromSomeDbOrServiceOrWhatever
if (products.length==0)
products = loader()
}
function saveProducts(saver) {
saver=saver || persistPrivateProductsToADbOrServiceOrWhatever
saver()
}
// Exported/public things
export function read(loader) {
loadProducts(loader);
return products;
}
export function add(product,loader,saver) {
loadProducts(loader);
products.push(product);
saveProducts(saver);
}
both exported functions can now use fetch/persist functions either by importing or as arguments.
Now the remaining is the mocking loader and saver function. saver function does not change anything so it can be null or empty. but if you want to check if it is called inside, then you need to mock it.
import {jest} from '#jest/globals'
import { read, add } from './productRepo';
it('can read products', () => {
loader=jest.fn().mockReturnValue([{id:7},{id:42}])
expect(read(loader).length).toBe(2);
expect(loader).toBeCalledTimes(1)
});
it('can add a product', () => {
loader=jest.fn().mockReturnValue([{id:7},{id:42}])
saver=jest.fn()
const oldNum = read(loader).length;
add({id:0, name:'test prod'},loader,saver);
expect(read(loader).length).toBe(oldNum+1)
expect(loader).toBeCalledTimes(0)
expect(saver).toBeCalledTimes(1)
});
There is a "gotcha" here. Since productRepo is imported once, loader is called in the first test but will not be called again in the second test since the first has already changed the products. Thus subsequent tests must take this into account when using non-class packages.
you must not get access to private properties or methodes anyway.
instead you can provide setter and getter for your properties.
for methodes I believe you can break it into some private parts and some public parts. private parts for your actual data source and public parts that can be used in test either.
I suggest implementing an initialize method on productRepo.js.
export function init(data) {
products = data
}
Then, you can init products with mocked data.
Also, if you can't change the file, you could use the rewire library, which lets you access non-exported functions and variables.

How to stub an inner function and set a variable value

This is one of the more complex scenarios I've encountered yet. I have a function that I need to test, this function is nested in a complex puzzle of functions. I need to stub this function, and set a value inside the variable inside.
For reasons I'm not allowed to share here, the variable inside the publishEvent() method is undefined during test run, I need a way to set the value for this variable during test in order for me to test the if block of code in the function.
I summarized the whole file because I can't share the code here due to NDA, sorry if the question is not detailed enough. Maybe using sinon I can directly set the value for this variable in the publishEvent function.
function emitEvent() {
async function publishEvent() {
const variable = library.fetchData()
if (variable) {
// do something
}
}
return Promise.resolve(publishEvent())
.then(() => { })
.catch(() => null);
}
function requestSuscription() {
return getAllSubscriptions()
.then((results) => {
return Promise.map(results, () => emitEvent())
})
}
function getAllSubscriptions() {
return new Promise()
}
function requestOtherSubscription() {
console.log('other thing requested')
}
module.exports = { requestSuscription, requestOtherSubscription }
I know I can stub the requestSuscription, requestOtherSubscription functions and it works, but how can I stub the publishEvent and set the variable value?
You need a way of controlling what library.fetchData() outputs. Either you need a way of injecting a substitute for that library (easiest option) or you need to employ a link seam (environment dependant, requires extra lib) - substituting the library with a fake one at the module loading level.
You can check out this SO answer to a nearly identical question for details.

what is the best method to multi request with callback or promise on node.js

I have a situation. I use Node.js to connect to a special hardware. let assume that I have two functions to access the hardware.
hardware.send('command');
hardware.on('responce', callback);
At first, I made a class to interface this to the application layer like this (I write simplified code over here for better understanding)
class AccessHardware {
constructor() {
}
updateData(callback) {
hardware.on('responce', callback);
hardware.send('command');
}
}
Now, the problem is that if there are multiple requests from the application layer to this access layer, they should not send multiple 'command' to the hardware. Instead, they should send one command and all of those callbacks can be served once the hardware answer the command.
So I update the code something like this:
class AccessHardware {
constructor() {
this.callbackList = [];
hardware.on('responce', (value) => {
while (this.callbackList.length > 0) {
this.callbackList.pop()(value);
}
});
}
updateData(callback) {
if (this.callbackList.length == 0) {
hardware.send('command');
}
this.callbackList.push(callback);
}
}
Of course, I prefer to use promise to handle the situation. so what is your suggestion to write this code with promise?
Next question, is this approach to make a 'list of callbacks' good?
I prefer to use promise to handle the situation. So what is your suggestion to write this code with promise?
You'd store a promise in your instance that will be shared between all method callers that want to share the same result:
class AccessHardware {
constructor(hardware) {
this.hardware = hardware;
this.responsePromise = null;
}
updateData() {
if (!this.responsePromise) {
this.responsePromise = new Promise(resolve => {
this.hardware.on('responce', resolve);
this.hardware.send('command');
});
this.responsePromise.finally(() => {
this.responsePromise = null; // clear cache as soon as command is done
});
}
return this.responsePromise;
}
}
Btw, if hardware is a global variable, there's no reason to use a class here.
Is the current solution to make a 'list of callbacks' good?
Yes, that's fine as well for a non-promise approach.

Node js. How to share an array between two functions? Is there any less complicated way

I am very new to nodejs and stuck at a place where one function populates an array and the other reads from it.
Is there any simple construct to synchronize this.
Code looks something like Below
let arr = [];
let prod = function() {
arr.push('test');
};
let consume = function() {
process(arr.pop());
};
I did find some complicated ways to do it :(
Thanks alot for any help... ☺️
By synchronizing you probably mean that push on one side of your application should trigger pop on the other. That can be achieved with not-so-trivial event-driven approach, using the NodeJS Events module.
However, in simple case you could try another approach with intermediary object that does the encapsulation of array operations and utilizes the provided callbacks to achieve observable behavior.
// Using the Modular pattern to make some processor
// which has 2 public methods and private array storage
const processor = () => {
const storage = [];
// Consume takes value and another function
// that is the passed to the produce method
const consume = (value, cb) => {
if (value) {
storage.push(value);
produce(cb);
}
};
// Pops the value from storage and
// passes it to a callback function
const produce = (cb) => {
cb(storage.pop());
};
return { consume, produce };
};
// Usage
processor().consume(13, (value) => {
console.log(value);
});
This is really a noop example, but I think that this should create a basic understanding how to build "synchronization" mechanism you've mentioned, using observer behavior and essential JavaScript callbacks.
You can use callback to share data between two functions
function prod(array) {
array.push('test1')
}
function consume() {
prod(function (array) {
console.log(array)
})
}

Calling a yeoman generator after a generator has finished

I am looking to call another yeoman generator once the first generator has finished installing, this will be based on an answer I give for one of the prompts.
I have tried calling it at the end.
end: function () {
this.installDependencies({
callback: function () {
if( this.generator2 ){
shell.exec('yo generator2');
}
}.bind(this)
});
},
This runs generator2, but I am unable to answer any prompts.
These are 2 separate generators, so I cannot make the second a sub generator.
Use Yeoman composability feature.
About the code, don't use this.installDependencies() callback (that won't work as you expect). Rather use the run loop priorities.
Also, you should review your logic and the way you think about your current problem. When composing generators, the core idea is to keep both decoupled. They shouldn't care about the ordering, they should run in any order and output the same result. Thinking about your code this way will greatly reduce the complexity and make it more robust.
I see this is an older question, but I came accross a similar requirement & want to make sure all options are listed. I agree with the other answers that it is the best choice to use the composability feature & keep the order irrelevant. But in case it really is necessary to run generators sequentially:
You can also execute another generator using the integration features.
So in generator1 you could call
this.env.run('generator2');
This will also let you answer prompts in generator2.
When using .composeWith a priority group function (e.g.: prompting, writing...) will be executed for all the generators, then the next priority group. If you call .composeWith to generatorB from inside a generatorA, then execution will be, e.g.:
generatorA.prompting => generatorB.prompting => generatorA.writing =>
generatorB.writing
You can cover all possible execution scenarios, condition checking with this concept, also use the options of .composeWith('my-genertor', { 'options' : options })
If you want to control execution between different generators, I advise you to create a "main" generator which composes them together, like written on http://yeoman.io/authoring/composability.html#order:
// In my-generator/generators/turbo/index.js
module.exports = require('yeoman-generator').Base.extend({
'prompting' : function () {
console.log('prompting - turbo');
},
'writing' : function () {
console.log('prompting - turbo');
}
});
// In my-generator/generators/electric/index.js
module.exports = require('yeoman-generator').Base.extend({
'prompting' : function () {
console.log('prompting - zap');
},
'writing' : function () {
console.log('writing - zap');
}
});
// In my-generator/generators/app/index.js
module.exports = require('yeoman-generator').Base.extend({
'initializing' : function () {
this.composeWith('my-generator:turbo');
this.composeWith('my-generator:electric');
}
});

Resources