How would I detect and group recurring methods? - javascript

How would I go about detecting recurring methods and grouping them by an identifier when calls to the recurring methods are asynchronous?
To demonstrate the use of time taken before the callback is called, setTimeout is being used.
var counter = 0
var foo = (function () {
var context
return function foo (callback) {
if (!context) {
context = {id: ++counter}
}
setTimeout(function () {
callback.call(context)
context = null
}, 1)
}
}())
foo(function () {
console.log(1, this.id)
foo(function () {
console.log(2, this.id)
})
})
foo(function () {
console.log(3, this.id)
})
The above code produces:
1 1
3 undefined
2 undefined
The desired result is:
1 1
3 2
2 1
Ideally, this would be achieved without having to use anything like .bind on the foo calls.
I've experimented briefly with arguments (more specifically arguments.callee) and am aware I most likely need some way of duplicating foo with different ids, though I couldn't get any results which persisted the id on the arguments.callee function returned.
EDIT: Thanks for the answers so far! These are perfect answers for the question, but my use case does take a step further.
In the current scenario, the callback may be called asynchronously at an indeterminate time, meaning context slides back to null before I need it to.
I've edited the above code and explanation to reflect that new issue.

You could create a variable in a closure around the foo method that stores if the method is currently "being called". It's hard to explain, but this is what it would look like:
var foo = (function() {
var locked;
return function (callback) {
if (!locked) {
counter += 1;
this.id = counter;
}
locked = true;
// Any time this callback calls 'foo',
// it will see it is locked and not increase the id
callback();
locked = false;
}
}());
var counter = 0;
var foo = (function() {
var locked;
return function(callback) {
if (!locked) {
counter += 1;
this.id = counter;
}
locked = true;
// Any time this callback calls 'foo',
// it will see it is locked and not increase the id
callback();
locked = false;
}
}());
foo(function() {
log([1, this.id])
foo(function() {
log([2, this.id])
})
})
foo(function() {
log([3, this.id])
})
function log(msgs) {
document.body.insertAdjacentHTML("beforeend", "<code>" + msgs.join(" ") + "</code><br />"); }

Related

Why isn't the variable in this object modified by its callback function?

I'm trying to get a global object to modify one of its own variables in a callback function initialised by one of its own methods. The callback appears to work, but the variable doesn't seem to have been modified when testing the global variable.
Why is the global object not being modified? Are the changes to the global object kept in some sort of staging area pending completion of the callback function?
let obj;
function test_object_flag() {
// 5 - check whether the "timer_finished" flag has been set
console.log("is the timer finished? " + obj.timer_finished); // should return "true"???
}
class TestClass {
constructor() {
this.timer_finished = false;
}
start_timer(ptr_callback_function) {
// 3 - set up a callback for the timer
setTimeout(function() {
// 4 - once the timer is done, set a "timer_finished" flag, and call the callback
this.timer_finished = true;
ptr_callback_function();
}, 1000);
}
}
$( document ).ready(function() {
// 1 - make a new onbject of type TestClass
obj = new TestClass();
// 2 - start the timer
obj.start_timer(test_object_flag);
});
The problem is that setTimeout creates it's own this. Solution may looks like:
start_timer(ptr_callback_function) {
// savig this that your need
const self = this;
setTimeout(function() {
// use needed context
self.timer_finished = true;
ptr_callback_function();
}, 1000);
}
Another option is to use arrow functions:
start_timer(ptr_callback_function) {
setTimeout(() => {
this.timer_finished = true;
ptr_callback_function();
}, 1000);
}

When can a function be only called once?

This problem is giving me trouble:
Write a function, once, (see: http://underscorejs.org/#once) that
takes a function and returns a version of that function which can only
be called once. [Hint: you need a closure] You probably don't want to
be able to double charge someone's credit card. Here is an example of
how to use it:
var chargeCreditCard = function(num, price){
//charges credit card for a certain price
};
var processPaymentOnce = once(chargeCreditCard);
processPaymentOnce(123456789012, 200);
Here's how I tried to solve it:
var once = function(func) {
var invoked = 0;
return function() {
if (invoked === 0) {
invoked++;
return func();
}
};
};
The only problem I can see is you are not passing the arguments to the called function. You can use the arguments object and Function.apply() to do this.
var once = function (func) {
var invoked = 0;
return function () {
if (invoked === 0) {
invoked++;
return func.apply(this, arguments);
}
};
};
Demo: Fiddle
You are almost in the right path but you could also store the return value, pass the args and provide the this context:
function once(func) {
var val,
count = 2;
return function () {
if (--count > 0) {
val = func.apply(this, arguments);
} else {
//performance concern
func = null;
}
return val;
};
}
This is what I have borrowed from lodash to use in my codebase.
It is also worth noting that, passing the count variable as an argument would also let us to use it in a way that the func gets called less than count times

Updating an array inside a call back

I have the following two functions:
var abc;
function updateNum() {
abc=0;
g.dbm.transaction("leagues").objectStore("leagues").openCursor(null, "prev").onsuccess = function (event) {
var teams, i;
team.filter({
attrs: ["tid", "abbrev", "region", "name", "cid"],
seasonAttrs: ["winp", "playoffRoundsWon"],
season: g.season
}, function (teams) {
// Sort teams by playoffs and winp, for first round
teams.sort(function (a, b) {
if (a.playoffRoundsWon < b.playoffRoundsWon) {
return -1;
}
if (a.playoffRoundsWon > b.playoffRoundsWon) {
return 1;
}
return a.winp - b.winp;
});
abc+=1;
});
};
}
function getNum() {
return abc;
}
What I am trying to do is update the variable abc inside the callback function and then return it. I do this by first calling the updateNum() function in another file. Then I assign a variable to the value of getNum()
Here is how a sample code would look like:
myFile.updateNum();
var number = myFile.getNum();
I am currently unable to return the updated value of num. number keeps returning 0 (the default value) instead of the newly updated value (which is 1).
How can I get it to show an updated value? Please let me know if I need to add any more information.
Well, if updateNum is async, it would have to take a callback as argument so that you can be notified when the number was updated.
E.g.
var num = 0;
function updateNumAsync(callback) {
setTimeout(function () {
num = 1;
callback && callback(num); //call the callback if provided
}, 500);
}
updateNumAsync(function (num) {
console.log(num); //updated num
});
Here is a general pattern for using an asynchronous function with a callback to pass the asynchronous results around. What is team.filter? You will need to design your code such that the asynchronous portion calls a callback() function that was passed to the enclosing function.
If filtering gives you problems you may want to look at https://github.com/caolan/async#filterarr-iterator-callback
(function main(){
getNum(function(err, abc){
console.log('thx for playing '+abc)
});
})();
function getNum(anotherCallback) {
// Whatever code relies on the result of an asynchronous function must be
// placed inside the callback function
countTeams(function(abc){
console.log('countTeams completed, abc='+abc);
var err = null;
anotherCallback(err, abc);
});
};
function countTeams(callback){
var abc=0;
g.dbm.transaction("leagues").objectStore("leagues").openCursor(null, "prev").onsuccess = function (event) {
var teams, i;
// I don't know what this filter function does, I am assuming it's synchronous
team.filter({
attrs: ["tid", "abbrev", "region", "name", "cid"],
seasonAttrs: ["winp", "playoffRoundsWon"],
season: g.season
}, function (teams) {
// Sort teams by playoffs and winp, for first round
teams.sort(function (a, b) {
if (a.playoffRoundsWon < b.playoffRoundsWon) {
return -1;
}
if (a.playoffRoundsWon > b.playoffRoundsWon) {
return 1;
}
return a.winp - b.winp;
});
abc+=1;
});
return callback(abc); // 0 or n depending on what team.filter does
};
};

Javascript: How do I tweak my debounce function to take an IF conditional?

I found a bug, and tracked it down.
You can see a simplified example of my code here.
As it turns out, I need to debounce my if() statement rather than debouncing the function itself.
I'd like to keep the debounce as a standalone function, but I'm not sure then how to pass the conditional in.
Any pointers?
Here's the code:
var foo = function(xyz) {
alert(xyz);
};
function setter(func, arg1, arg2) {
return {
fn: func,
arg1: arg1,
arg2: arg2
};
}
function debounce(someObject) {
var duration = someObject.arg2 || 100;
var timer;
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(function() {
someObject.fn(someObject.arg1);
timer = 0;
}, duration);
}
var toggle = true;
if (toggle) {
debounce(setter(foo, 'The best things in life are worth waiting for.', 1250));
} else {
foo('Instant gratification is sweet!!');
}
Using your example, why not pass toggle in as arg 1... something like:
var toggle = true;
var debouncedFunk = function(toggle) {
if (toggle)
// the function call
else
// something else
};
debounce(debouncedFunk, toggle, 1250);
You should also look into using the Function objects .call and .apply methods. They are for calling the function and passing in arguments. Taking the example function:
var example = function(one, two) {
// Logic here
};
You can call it in three ways:
// First
example(1, 2);
// Second
example.call({}, 1, 2);
// Third
example.apply({}, [ 1, 2 ]);
The first is the standard way to call a function. The difference between the first and the .call is that the first parameter to .call is the context object of the function (what this will point to inside the function), the other parameters are passed after that (and a known list is required for .call. The benefit of .apply is that you can pass an array to the function of arguments and they will be assigned to the parameter list appropriately, the first parameter is still the context object.
It would simplify your debounce function, instead of having to deal with a structured object as you currently do.
A suggestion for your debounce:
var debounce = function(funk, delay) {
var args = [];
if (arguments.length > 2)
args = [].slice.call(arguments, 2);
setTimeout(function() { funk.apply({}, args); }, delay);
};
Changing your current if to:
var toggle = true;
var debouncedFunk = function(toggle) {
if (toggle)
// Do if true
else
// DO if false
};
debounce(debouncedFunk, 1000, toggle);
Maybe too much information (sorry)?
As a last note, I'd recommend using a framework (if possible) where these functions have been implemented already (and many other useful functions) such as Underscore. Using Underscore your example would look like:
// Define debouncedFunk and toggle
debouncedFunk = _.bind(debouncedFunk, {}, toggle);
debouncedFunk = _.debounce(debouncedFunk, 1000);
debouncedFunk();
EDIT
Fixed the underscore example, _.debounce returns a function that will execute only after the delay but it still needs to be called.

How do I find out how many times a function is called with javascript/jquery?

Perhaps an odd question but here it goes: I have a function which I call periodically and within that function I need to know which iteration I'm in, or how many times the function has been called. A simplified version of the problem:
jQuery( document ).ready( function(){
setInterval( "myFunction()", 3000 );
});
function myFunction()
{
alert( "I have been called X times" );
}
So, how do I figure out the X in the above code?
Easy version: make a global variable like in codeling's answer. The problem - if some other code also defines a global variable with the same name, you're both in trouble.
Easy extended version - give the variable a crazy name that nobody will ever use: calledTimesED7E69A7B141457CA8908A612E3D7A3A
Clever version: append that variable to an existing global variable. Remember - everything's an object in Javascript!
$(function(){ setInterval(myFunction, 3000); });
function myFunction()
{
myFunction.calledTimes++;
alert( "I have been called " + myFunction.calledTimes + " times" );
}
myFunction.calledTimes = 0;
Traditional version: use scoping to hide that variable.
$(function()
{
var calledTimes = 0;
setInterval(function()
{
calledTimes++;
alert( "I have been called " + calledTimes + " times" );
}, 3000);
});
This hides "myFunction" though, so let's try again with a tricky kind of scoping:
var myFunction = null;
(function()
{
var calledTimes = 0;
myFunction = function()
{
calledTimes++;
alert( "I have been called " + calledTimes + " times" );
}
})();
$(function () { setInterval(myFunction, 3000); });
... and there are a zillion other ways you would hide that variable with scoping. Just pick your favorite.
You could simply use a global variable, which is increased each time you call the function:
var myFuncCalls = 0;
function myFunction()
{
myFuncCalls++;
alert( "I have been called " + myFuncCalls + " times" );
}
As soon as your code gets a little more complex (or if you use a lot of other libraries), you should, however, consider using scoping as shown in the other answers here (best explained in the one by Vilx).
Here's another interesting solution that doesn't use an external variable. The best part about this is you can leave any pre-existing functions untouched and call them as you would normally. That means if you're attempting to "tap in" to an function in an existing library, this will work very well for you. It adds an unobtrusive counter and allows you to continue calling existing functions normally; even with arguments!
// no js library required
// pre-existing function
var a = function(){
console.log("pre-existing function function");
console.log("arguments:", arguments);
};
// add counter func
var addFnCounter = function(target){
var swap = target;
var count = 0;
return function(){
swap.apply(null, arguments);
count++;
console.log("func has been called " + count + " times");
console.log("\n");
};
};
// usage
a = addFnCounter(a);
// call a() as you would normally
a();
a(1,2,3);
a('hello', 'world');
// using your setInterval example
setInterval(a, 3000);
Output
pre-existing function function
arguments: []
func has been called 1 times
pre-existing function function
arguments: [1, 2, 3]
func has been called 2 times
pre-existing function function
arguments: ["hello", "world"]
func has been called 3 times
setInterval output
pre-existing function function
arguments: []
func has been called 4 times
pre-existing function function
arguments: []
func has been called 5 times
pre-existing function function
arguments: []
func has been called 6 times
See it working here on jsfiddle
You'll have to use a closure.
Normally you would use a static variable. in Javascript it would look something like:
jQuery( document ).ready( function(){
setInterval( myFunction, 3000 );
});
var myFunction = (function(){
var count = 0;
return function(){
count++
alert( "I have been called " + count + " times");
}
})();
Demonstration: http://jsfiddle.net/MZQ83/2/
A static variable is cleaner and it won't pollute your outer scope either, compared to a closure or a decorator as in other answers.
var foo = function(){
alert( ++foo.count || (foo.count = 1) );
}
// test
function callTwice(f){ f(); f(); }
callTwice(foo) // will alert 1 then 2
or
callTwice( function bar(){
alert( ++bar.count || (bar.count = 1) );
}); // will alert 1 then 2
the second one is a named anonymous function. And note this syntax:
var foo = function bar(){ /* foo === bar in here */ }
Create a global variable and initialize by zero. then increment by one when myfunction() called. Display that variable instead of X.
ES6 / ES2015
You can use a Proxy for your function utilising the apply() trap:
const addCounter = fn => {
let count = 0; // keep count
//define handler
const handler = {
apply() {
//do something with this counter
console.log(`I have been called ${++count} times `);
return Reflect.apply(...arguments); //call the function normally
}
}
//wrap the function into a proxy that uses the handler and return it
return new Proxy(fn, handler);
}
setInterval( addCounter(myFunction), 1000 );
function myFunction() { //sample operation - move an image
const img = document.querySelector("img");
let offset = img.offsetLeft + 10;
if (offset > 100) //return to start
offset = 0;
img.style.left = `${offset}px`;
}
img {
position: absolute;
}
.as-console-wrapper {
max-height: 45px !important;
}
<img src="https://picsum.photos/150" />
You can use an Immediately Invoking Function Expression (or IIFE) to create a closure around the counter function. You can do it like this with ES6 Arrow functions:
const counterFunction = (() => {
let counter = 0;
return () => console.log(++counter);
})();
counterFunction();
counterFunction();
counterFunction();
Or with normal function expressions:
var counterFunction = (function() {
var counter = 0;
return function() {
console.log(++counter);
};
})();
counterFunction();
counterFunction();
counterFunction();
Read more about IIFEs
Read more about closures
There is an inbuilt function in JS called console.count()
My approach would add a property “count” to the function itself.
Just add one line at the beginning of your function you want to have tracked calls.
function func() {
func.count = (func.count || 0) + 1;
// first time you call the function func.count is undefined so use 0 instead
console.log("hi");
}
func();
console.log(func.count) // 1
func();
func();
func();
console.log(func.count) // 4
Functions are objects in javascript after all. No pollution of global namespace, no wrapping or closures needed, very simple to understand and to write.

Categories

Resources