Batching functions synchronously? - javascript

This is a tricky problem. Is it possible to batch function calls without waiting for the next tick of the event loop?
Derived example for simplicity.
let numbers = []
let total = 0
const addNumber = (num) => {
numbers.push(num)
compute()
}
// for the sake of this argument let's say compute is very expensive to run
const compute = () => {
numbers.forEach((num) => {
total += num
})
numbers = []
}
This is currently what happens. compute runs after every addNumber because we need the total to be computed synchronously.
This is the example we need to work. We can only call addNumber we cannot directly call compute. Is there a way change addNumber so this example works but only calls compute once?
addNumber(2)
addNumber(4)
addNumber(10)
console.log(total === 16) // needs to be true
Thanks for any help!

No. What I'd suggest is move the placement of your compute function so it calculates the value of total just before you need to retrieve it.
Any other option I can think of would entail some kind of asynchronous functionality, which you've said you don't want.

Related

Calling a variable from inside another function is bad?

I want to make my JS code to be less repetitive with an organized look. But I don't know if calling a function from inside another function is a bad practice, like Global Variables.
I share a piece of the code here.
thanks.
function getEx() {
return document.getElementById('example')
}
function getExTwo() {
return document.getElementById("exampleTwo");
}
function getTheValue() {
let getExValue = getEx();
let getExTwoValue = getExTwo();
}
Calling a function from within another function is absolutely not bad coding. That's part of what functions are for, really -- breaking up logical processes into smaller pieces.
Here's an example of how this can work.
// Note: This is new ES6/ES7 syntax for writing JavaScript functions.
// I'm using it here because it's very terse.
const add = (a, b) => a + b;
const multiply = (a, b) => a * b;
const square = (a) => multiply(a, a);
const sumOfSquares = (arr) => {
let sum = 0;
arr.forEach(number => sum += square(number));
return sum;
};
In the (simplified) example above, we use different functions to break up the distinct logical pieces of the problem into smaller, more manageable problems. For example, to calculate the sum of the squares of the array [1, 10, 12], we want to be able to add things and we want to be able to square things, so it's a good idea to create functions for performing each of those steps. We might even want to use other functions within those functions (e.g. calling multiply from within square).
Now, is it possible to go overboard with creating new functions? Yes. Try to avoid writing multiple functions that are basically the same. But otherwise... go nuts!
Calling a function from within another function is not bad. It is a recommended way of reducing repetition by breaking your code into smaller pieces, each handling some specific logic.
Here is a simplified version of your code:
// ps: $ is not from jquery it is just a normal variable.
const $ = document.querySelector
const getValues = () => {
const firstVal = $('#example')
const secondVal = $('#exampleTwo')
}

mocking a function inside a function and getting calls count in jest

Considering this module in script.js:
const randomizeRange = (min, max) => {
return Math.floor(Math.random() * (max - min) + min);
};
const randomArr = (n, min, max) => {
const arr = [];
for (let i = 0; i < n; i++) {
arr.push(randomizeRange(min, max));
}
return arr;
};
module.exports = { randomArr, randomizeRange };
In the test file below, shouldn't the call count of randomizeRangeMock be 100 because it is being called inside randomArrMock which is called once? Why do I get 0
const randomArr = require("./script").randomArr;
const randomizeRange = require("./script").randomizeRange;
const randomArrMock = jest.fn(randomArr);
const randomizeRangeMock = jest.fn(randomizeRange);
test("tests for randomArr", () => {
expect(randomArrMock(100, 20, 1000)).toHaveLength(100);
expect(randomArrMock.mock.calls.length).toBe(1)
expect(randomizeRangeMock.mock.calls.length).toBe(100) //This fails because its 0
});
I appreciate that you are trying to just understand jest with this question and so I will answer as best I can. I'd normally recommend that you don't mock functions unless you are making some kind of complex back end call to a server or database or whatever. The more real testing you can do with real code (and not mocks) is infinitely more beneficial. That would be my tip for you going forward.
But for the purpose of this question - here's what's going on.
The reason you're not seeing your mocks work in the right way here is because you've essentially created two separate mocks:
You've created a single mock for randomizeArr.
You've created another single, separate mock for randomizeRange.
When you invoke randomArrMock(100, 20, 1000) in your test, this will invoke your single mock for randomizeArr as you've designated but that mock has absolutely no concept of the other mock for randomizeRange ever existing. And so it's never invoked behind the scenes. So this is why you see a 0 for the amount of calls at the end.
It's just a matter of changing your setup a little bit here to designate which mocks are invoked and when in order to see both work together as you're expecting.
What I would actually choose here is not to mock your randomizeRange method at all. That method invoked Math.floor and that's going to be much easier to "spy on" (which is a hint for the method we're going to use here) in this case. That Math.floor method is invoked once with each call to randomizeRange and so it will be an ideal choice. It should also be called 100 times, the same as your method.
The reason we're choosing to spyOn the method rather than to use an actual mock in the way you have done already is because we want to keep the original behaviour and so spying on how many times the method is called without overwriting the intended behaviour is ideal.
So keep your existing mock for randomizeArr as follows:
const randomArrMock = jest.fn(randomArr);
Now set up the spy for Math.floor
const mathFloorSpy = jest.spyOn(Math.prototype, `floor`);
This "spy" will effectively spy on this method without overwriting its behaviour. You can overwrite the behaviour if you want to but we want to keep the behaviour intact while just counting how many times it is called.
So now when you run your test suite:
test("tests for randomArr", () => {
expect(randomArrMock(100, 20, 1000)).toHaveLength(100);
expect(randomArrMock.mock.calls.length).toBe(1)
expect(mathFloorSpy.mock.calls.length).toBe(100)
});
This will now pass as Math.floor will have been invoked each time your randomizeRange method was called.
Finally don't forget to restore the original Math.floor functionality when your test is finished by using:
mathFloorSpy.mockRestore();
Happy testing!
This seems to be a common question, and the most "official" solution I can find was on the Jest GitHub issues page, at https://github.com/facebook/jest/issues/936#issuecomment-545080082.
Since you are using require instead of ES modules transpiled with babel, the solutions described in that linked issue don't seem to apply as well.
I would modify your script.js module to directly reference the exports used, so that the mocks refer to the correct function:
exports.randomizeRange = (min, max) => {
return Math.floor(Math.random() * (max - min) + min);
};
exports.randomArr = (n, min, max) => {
const arr = [];
for (let i = 0; i < n; i++) {
arr.push(exports.randomizeRange(min, max));
}
return arr;
};
And then your test implementation would reference the mocked functions:
const myScript = require('../script');
jest.spyOn(myScript, 'randomArr');
jest.spyOn(myScript, 'randomizeRange');
test("tests for randomArr", () => {
expect(myScript.randomArr(100, 20, 1000)).toHaveLength(100);
expect(myScript.randomArr).toHaveBeenCalledTimes(1);
expect(myScript.randomizeRange).toHaveBeenCalledTimes(100);
});
Using spyOn and toHaveBeenCalledTimes improves readability here.
Now, giving me own piece of advice: Write testable code. If your code is testable, well, you can easily write tests for it, and testable code is generally more modular and flexible; but don't focus on testing implementation details.
In your example, if you want to expose a randomArr method, then don't also expose randomizeRange; your randomArr method can call randomizeRange, but that is an implementation detail you shouldn't worry about.
If you want to make your randomArr much easier to test, consider making the range method (or the range) a parameter. You can then test it without testing some implementation of how it generates random ranges. Something like this:
exports.randomArr = (n, min, max, randomRangeGenerator) => {
const arr = [];
for (let i = 0; i < n; i++) {
arr.push(randomRangeGenerator(min, max));
}
return arr;
};
You can extend this pattern to randomizeRange as well. For testing that method, either pass in the floor and random functions as parameters, or mock them (through the Math object) in your tests. Working with Math.random in tests is difficult since it will produce different values on each run, so it's important mock that.

Force specific input with JSVerify

I'm starting to use JSVerify for property based testing.
Given a function (just an example) that accepts a string as parameter, I use JSVerify to pass in a large number of arbitrary strings and see if the function behaves as intended for all of them. It turns out that there are some strings that make the test fail: so far I've found that if the string contains \0000, \0001 or \n, the test fails.
I want to correct my code so that this cases are handled accordingly, but in order to prevent regression, I want to make sure that each one of this cases is included in every test run. I also want to avoid hardcoding the number generator's seed (rngState) since that would prevent the discovery of additional corner cases in the future.
To clarify: suppose that I'm testing my function foo():
jsc.assert(jsc.forall("string", (str) => {
const result = foo(str)
return (/* some expression that evaluates whether the result is correct */)
}))
This test is feeding 100 random strings into foo() every time a run the test suite. Initially it's passing. After some runs it suddenly fails because, precisely this time, the random string generator has generated a string containing the character \0000, which my function doesn't handle as expected. From now on, I would like that my test uses this string as input every time, plus the usual 100 random inputs.
Is there a built-in way to do this using JSVerify? Or should I just treat these inputs as separate test cases?
This is how i'm addressing the problem you mentioned. Instead of testing specific values, my code can specify as many "bad" seeds as needed, in addition to running a random one with each test invocation.
Notes:
1) I am using Mocha
2) I am using async code, so I return a promise
3) the JSON.stringify allows me to easily see the seed given the above
You can still use this method without 1,2,3 above with some refactoring:
const _ = require('lodash');
var jsc = require('jsverify');
// mocha's describe
describe("StackOverflow", function () {
it('example1', function(done) {
let t = jsc.forall('array nat', function (in1) {
let asComplex = foo(in1);
let backAgain = bar(asComplex);
return new Promise(function(resolve, reject) {
setTimeout(() => {
resolve(_.isEqual(backAgain,in1));
},500); // setTimeout
}); // promise
}); // forall
let props = {size:0xffffffff, tests: 1000};
jsc.check(t, props).then( r => r === true ? done() : done(new Error(JSON.stringify(r))));
props.tests = 3;
jsc.check(t, props).then( r => r === true ? done() : done(new Error(JSON.stringify(r))));
props.rngState = "8e1da6702e395bc84f";
jsc.check(t, props).then( r => r === true ? done() : done(new Error(JSON.stringify(r))));
// add more seeds here
}); // it
}); // describe

Append items ordering by placed amount

I'm using this function to append new items in order by the amount. This function is being called every 30-50ms.
var insertBefore = false;
container.find('.roll-user-row[data-user-id="' + user_data.id + '"]').remove();
container.children().each(function () {
var betContainer = $(this), itemAmount = $(this).attr('data-amount'), betId = $(this).attr('data-user-id');
if (itemAmount < betData.totalAmount) {
insertBefore = betContainer;
return false;
}
});
if (insertBefore) {
$(template).insertBefore(container);
} else {
container.prepend(template);
}
itemAmount = $(this).attr('data-amount') is integer, betData.totalAmount is interger too. And if appending goes slower than ±300ms - everything works well. In case of fast appending I get this result:
and thats not even close what I want - thats random. How to solve this?
1. Refactoring
First of all, return within .each callback doesn't work. It just breaks current iteration, not all the cycle. If you want to interrupt cylce, you should use simple for-loop and break statement. Then, I would recommend to call $() as rarely as possible, because this is expensive. So I would suggest the following refactoring for your function:
function run() {
container.find('.roll-user-row[data-user-id="' + user_data.id + '"]').remove();
var children = container.children();
for (var i = 0; i < children.length; i++) {
var betContainer = $(children[i]); // to cache children[i] wrapping
var itemAmount = betContainer.attr('data-amount');
var betId = betContainer.attr('data-user-id');
if (itemAmount < betData.totalAmount) {
$(template).insertBefore(container);
return; // instead of "break", less code for same logic
}
}
container.prepend(template); // would not be executed in case of insertBefore due to "return"
}
2. Throttling
To run a 50ms repeating process, you are using something like setInterval(run, 50). If you need to be sure, that run is done and this is 300ms delay, then you may use just setInterval(run, 300). But if the process initializes in a way that you can't change, and 50ms is fixed interval for that, then you may protect run calling by lodash throttle or jquery throttle plugin:
var throttledRun = _.throttle(run, 300); // var throttledRun = $.throttle(300, run);
setInterval(throttledRun, 50);
setInterval is just for example, you need to replace your initial run with throttled version (throttledRun) in your repeater initialization logic. This means that run would not be executed until 300ms interval has passed since the previous run execution.
I am only posting the approach here, if my understanding is right, then I'll post a code. First thing came to my mind reading this was the 'Virtual DOM' concept. Here is what you can do,
Use highly frequent random function calls only to maintain a data structure like an object. Don't rely on DOM updates.
Then use a much less frequent setInterval repetitive function call to redraw (or update) your DOM from that data structure.
I am not sure there are any reason you can't take this approach, but this will be the most efficient way to handle DOM in a time critical use-case.

understanding setInterval in javascript

I have a function which does something async like saving to database. Want a mechanism that first inserts the row and the next insertion should occur only when the first insert operation has finished.
Here is what I have tried and it somewhat works.
var interval = true;
function insert() {
model.save(function () {
interval = true;
})
}
foreach(row, function (key, val) {
var interval1 = setInterval(function () {
if (interval) {
insert();
interval = false;
clearInterval(interval1);
}
}, 100)
})
Is it the correct approach of doing this? Please shed some light about my understanding of timers in javascript.
No, you should not be creating timers to poll for when something is done. That's probably the worst way you can do it. What you want to do is to explicitly start the next iteration each time the previous one finishes.
Here's the general idea for how you do this without polling. The idea is that you need to create a function that can be called successive times and each time it's called, it will perform the next iteration. You can then call that function from the completion handler of your async operation. Since you don't have a nice convenient foreach loop to control the iteration, you then have to figure out what state variables you need to keep track of to guide each iteration. If your data is an array, all you need is the index into the array.
function insertAll(rows) {
// I'm assuming rows is an array of row items
// index to keep track of where we are in the iteration
var rowIndex = 0;
function insert() {
// keep going as long as we have more rows to process
if (rowIndex < rows.length) {
// get rows[rowIndex] data and do whatever you need to do with it
// increment our rowIndex counter for the next iteration
++rowIndex;
// save and when done, call the next insert
model.save(insert)
}
}
// start the first iteration
insert();
}
If you don't have your data in an array that is easy to step through one at a time this way, then you can either fetch each next iteration of the data when needed (stopping when there is no more data) or you can collect all the data into an array before you start the operation and use the collected array.
No, this is absolutely not the right way to do this. Lets assume that row contains 10 values, then you are creating 10 independent timers which continuously run and check whether they can insert. And it's not even guaranteed that they are executed in the order they are created.
As jfriend00 already mentioned, you should omit the "loop" and make use of the completion callback of the save operation. Something like this:
var rows = [...];
function insert(rows, index) {
index = index || 0;
var current_element = rows[index];
model.save(function() {
if (index < rows.length - 1) {
insert(rows, index + 1);
}
});
}
insert(rows);
Notice how the function calls itself (somehow) after the save operation is complete, increasing the index so the next element in the array is "saved".
I would use a library that handles async stuff such as async.js
BTW it seems like your model.save methods takes a callback, which you can use directly to call the insert method. And if the insert function is one you have made by yourself, and not a part of some bigger framework, I will suggest to re-write it and make take a callback as parameter, and use that instead of using setInterval for checking when async work is done.

Categories

Resources