Is is bad practice to pass an empty callback in Javascript? - javascript

I have a long running function that I don't really care about handling properly. Is it bad practice to just hand it off to the event loop along with an empty callback and move on. Something like this:
var takeSomeTime = function(callback) {
var count = 0,
max = 1000,
interval;
interval = setInterval(function() {
count++;
if (count === max) {
interval.clearInterval();
return callback();
}
}, 1000);
};
var go = function(callback) {
// do some stuff
takeSomeTime(function(err) {
if (err) {
console.error(err)
}
// take all the time you need
// I'm moving on to to do other things.
});
return callback();
};
go(function(){
// all done...
});

I don't know how your question is related to memory leaks, but one could certainly think of useful applications of passing empty function around in general. You basically could pass an empty function to third party code, that expects a function and doesn't check if it actually got one. Just like in your example, or this small logging library:
// Javascript enum pattern, snatched from TypeScript
var LogLevel;
(function (LogLevel) {
LogLevel[LogLevel["DEBUG"] = 0] = "DEBUG";
LogLevel[LogLevel["WARN"] = 1] = "WARN";
LogLevel[LogLevel["ERROR"] = 2] = "ERROR";
LogLevel[LogLevel["FATAL"] = 3] = "FATAL";
})(LogLevel || (LogLevel = {}));
// end enum pattern
var noLog = function() {}; // The empty callback
function getLogger(level) {
var result = {
debug: noLog,
warn: noLog,
error: noLog
};
switch(level) {
case LogLevel.DEBUG:
result.debug = console.debug.bind(console);
case LogLevel.WARN:
result.warn = console.warn.bind(console);
case LogLevel.ERROR:
result.error = console.error.bind(console);
}
return result;
}
var log1 = LogFactory.getLogger(LogLevel.DEBUG);
var log2 = LogFactory.getLogger(LogLevel.ERROR);
log1.debug('debug test');// calls console.debug and actually displays the
// the correct place in the code from where it was called.
log2.debug('debug test');// calls noLog
log2.error('error test');// calls console.error
You basically return the empty function noLog back to the consumer of our library in order to disable logging for a particular log level, yet it can be called with any number of arguments without raising errors.

Related

Design pattern for repeating a set of methods multiple times

Imagine we're writing a spreadsheet validation function. The user can enter multiple values in the spreadsheet, and there is a method that will verify if the values are correct. In addition to verifying if they're correct, there is also a "fix it for me" dialog that pops up and asks the user if they want to fix the problem automatically.
For example purposes, let's say we have the following fields:
Event url: The link to an event.
Event title: the name of the calendar event.
Invitees: a list of comma separated email addresses of users that should be invited to the event.
The user can then hit a "validate" button that will check the following:
That the Event title really matches the one in the URL. If it doesn't they are presented with an option to update the title.
That the invitees are all on the event. If they aren't, an option to invite the next one is presented to the user (this is only done once at a time).
What's a good programming design pattern to execute a set of functions over and over again?
function validateSpreadsheet() {
validateEventTitle();
validateInvitees();
}
Both validateEventTitle and validateInvitees should return one of 3 possible values:
Success
Retry (the user chose to use the "fix it for me" button.)
Error (the user didn't choose the "fix it" feature.)
If one of them returns Retry, the entire method validateSpreadsheet should be run (e.g. in case we decide to have the event title depend on the number of invitees).
I can think of several ways the function validateSpreadsheet could repeat its logic:
(A) While loop
(B) Recursion
(C) Array of functions
I can think of several ways the function validateEventTitle can report its status:
(1) it could return an enum with the 3 values (success, retry, error)
(2) it could raise an exception in the case of retry and/or error
I implemented pseudocode for solution C1 (see the end of the post), but C1 makes it hard to share code between the different methods. For example, if the meat of the code looked something like this:
function validateSpreadsheet() {
var row = getRow();
var title = getEventTitle(row);
validateEventTitle(title, row);
validateInvitees(row);
}
... that would be more difficult to get working with C1 since the methods are wrapped in functions. I realize there are ways to workaround this limitation.
I don't like solution B1, but for completeness sake, I included a version of it below too. I don't like that it uses the call stack for repetition. I also think the code is pretty messy with the double if checks. I realize I could create helper methods to make it a single if check for each method, but that's still pretty messy.
I implemented a working example of solution A2. This one seems to work well, but it heavily exploits exceptions in a way that would probably confuse a new programmer. The control flow is not easy to follow.
Is there already a design pattern to achieve something like this? I'd like to use that rather than reinventing the wheel.
Solution C1 (Pseudocode)
function solutionC1() {
var functions = [
method1,
method2
];
while (true) {
var result = SUCCESS;
for (var f in functions) {
result = f();
if (result === SUCCESS) {
continue;
} else if (result === REPEAT) {
break;
} else {
return result; // ERROR
}
}
if (result === REPEAT) {
continue;
} else {
return; // SUCCESS
}
}
}
Solution B1 (Pseudocode)
function solutionB1() {
var result;
result = method1();
if (result === RETRY) {
return solutionB1();
} else if (isError(result)) {
return result;
}
result = method2();
if (result === RETRY) {
return solutionB1();
} else if (isError(result)) {
return result;
}
}
Solution A2 (Working with unit tests)
function solutionA2() {
while (true) {
try {
// these two lines could be extracted into their own method to hide the looping mechanism
method1();
method2();
} catch(error) {
if (error == REPEAT) {
continue;
} else {
return error;
}
}
break;
}
}
var REPEAT = "REPEAT";
var method1Exceptions = [];
var method2Exceptions = [];
var results = [];
function unitTests() {
// no errors
method1Exceptions = [];
method2Exceptions = [];
results = [];
solutionA2();
if (results.join(" ") !== "m1 m2") { throw "assertionFailure"; }
// method1 error
method1Exceptions = ["a"];
method2Exceptions = ["b"];
results = [];
solutionA2();
if (results.join(" ") !== "m1:a") { throw "assertionFailure"; }
// method1 repeat with error
method1Exceptions = [REPEAT, "a"];
method2Exceptions = ["b"];
results = [];
solutionA2();
if (results.join(" ") !== "m1:REPEAT m1:a") { throw "assertionFailure"; }
// method1 multiple repeat
method1Exceptions = [REPEAT, REPEAT, REPEAT, "a"];
method2Exceptions = ["b"];
results = [];
solutionA2();
if (results.join(" ") !== "m1:REPEAT m1:REPEAT m1:REPEAT m1:a") { throw "assertionFailure"; }
// method1 multiple repeat, method2 repeat with errors
method1Exceptions = [REPEAT, REPEAT, REPEAT];
method2Exceptions = [REPEAT, REPEAT, "b"];
results = [];
solutionA2();
if (results.join(" ") !== "m1:REPEAT m1:REPEAT m1:REPEAT m1 m2:REPEAT m1 m2:REPEAT m1 m2:b") { throw "assertionFailure"; }
// method1 multiple repeat, method2 repeat with no errors
method1Exceptions = [REPEAT, REPEAT, REPEAT];
method2Exceptions = [REPEAT, REPEAT];
results = [];
solutionA2();
if (results.join(" ") !== "m1:REPEAT m1:REPEAT m1:REPEAT m1 m2:REPEAT m1 m2:REPEAT m1 m2") { throw "assertionFailure"; }
// [REPEAT, "Test"];
}
function method1() {
// in reality, this method would do something useful, and return either success, retry, or an exception. To simulate that for unit testing, we use an array.
var exception = method1Exceptions.shift();
if (typeof exception !== "undefined") {
results.push("m1:" + exception);
throw exception;
} else {
results.push("m1");
}
}
function method2() {
// in reality, this method would do something useful, and return either success, retry, or an exception. To simulate that for unit testing, we use an array.
var exception = method2Exceptions.shift();
if (typeof exception !== "undefined") {
results.push("m2:" + exception);
throw exception;
} else {
results.push("m2");
}
}
unitTests();
For concise, clean code, I'd suggest having the functions that result in errors actually throw the errors, if they don't do so already. This allows for any errors thrown to immediately percolate up to the top to a containing try block:
const fns = [
method1,
method2
];
// If the methods return errors but don't throw them, pipe them through isError first:
const fnsThatThrow = fns.map(fn => () => {
const result = fn();
if (isError(result)) {
throw new Error(result);
}
return result;
});
Then, all you have to do is check whether either function call results in REPEAT (in which case, recursively call validateSpreadsheet), which can be achieved with Array.prototype.some:
function validateSpreadsheet() {
if (fnsThatThrow.some(fn => fn() === REPEAT)) {
return validateSpreadsheet();
}
}
try {
validateSpreadsheet();
} catch(e) {
// handle errors
}

Needing some visitor-like design pattern

I will give you a sample example of my problem to remove the logical complexity and let you be focus on the important part. Of course, this example will be a bit useless...
I have a tree structure where node are like that
{
path: "...",
childs : []
}
Now, I have to write all the full paths from root to each leaf in an array.
My design is very poor:
function listPaths(node) {
var result = [];
function listForNode(n, parentFullPath) {
var thisPath = parentFullPath + "/" + n.path;
result.push(thisPath);
n.childs.forEach(function (child) {
listForNode(child, thisPath);
});
}
listForNode(node, "");
return result;
}
It could be nice but I can't write the test with Mocha without having an insane 600 line code test file. At this moment, you should be asking why. The reason is the complexity of the real purpose, that's not relevant for my question. My goal is to having something 'mockable' cause I'm used to. (Java dev). But I fail.
Do you have any pattern that I can use to resolve this one? I'm not really good at JS patterns. :/
Visitor? Making an Y Combinator? So many possibility...
Thank you for reading me
You need to remember that functions are first class citizens in javascript.
I see that essentially what you have is something like
function createVisitor(parentsAccumulatorInitialValue, parentsAccumulator){
var visitor = function myVisitor (node) {
var result;
function listForNode(n, parentsAcc) {
var thisPath = parentsAccumulator(parentsAcc, n);
result.push(thisPath);
n.childs && n.childs.forEach(function (child) {
listForNode(child, thisPath);
});
}
result = [];
listForNode(node, parentsAccumulatorInitialValue());
return result;
}
return visitor;
}
var listPaths = createVisitor(
function parentInit () {
return "";
},
function parentAcc (parentFullPath, n) {
return parentFullPath + "/" + n.path;
});
But that's not the only abstraction you could take care of:
function createVisitor2(
totalAccumulatorInitialValue,
totalAccumulator,
parentsAccumulatorInitialValue,
parentsAccumulator){
var visitor = function myVisitor (node) {
var total;
function listForNode(n, parentsAcc) {
var thisPath = parentsAccumulator(parentsAcc, n);
total = totalAccumulator(total, thisPath, n);
n.childs && n.childs.forEach(function (child) {
listForNode(child, thisPath);
});
}
total = totalAccumulatorInitialValue();
listForNode(node, parentsAccumulatorInitialValue());
return total;
}
return visitor;
}
var listPaths2 = createVisitor2(
function totalInit() {
return [];
},
function totalAcc(total, thisPath, n){
total.push(thisPath);
return total;
},
function parentInit () {
return "";
},
function parentAcc (parentFullPath, n) {
return parentFullPath + "/" + n.path;
});
Which might be pretty reasonable, but as you can see, I'm already beginning to have trouble finding appropriate names for these variables. In fact, I'd say the name of our function is bad, as doesn't create anything strictly like a visitor object I know of. However, it does work (BTW, I've slightly modified it to handle nulls as well as empty arrays):
> listPaths( { path:"foo",
childs: [{path:"bar", childs: null}, {path:"bob", childs: null}]})
["/foo", "/foo/bar", "/foo/bob"]
It can be modified even further so that your trees don't strictly even have the same structure... but we're already at 4 parameters, which isn't great. It'd be better if your visitor creator were passed a single extensible object with all the necessary methods or values. For instance, maybe (pseudocode):
function createVisitor3(opts) {
//assume we've defined GetDefaults() somewhere local to createVisitor3
// as well as assume that extend is defined somewhere that copies properties
// into a new object like various previously existing libraries do.
opts = extend({}, GetDefaults(), opts);
var totalAccumulatorInitialValue = opts.totalAccumulatorInitialValue;
var totalAccumulator = opts.totalAccumulator;
var parentsAccumulatorInitialValue = opts.parentsAccumulatorInitialValue;
var parentsAccumulator = opts.parentsAccumulator;
var childrenGetter = opts.childrenGetter;
/// etc.
...
}

How to properly return an empty function?

I'm using a run-time assignment of functions to account for browser differences. However for un-supported browsers, I want to return an empty function so that a JavaScript error is not thrown.
But, jslint complains about empty functions. What is the jslint happy way to do this?
Empty block.
$R.functionNull = function () {
// events not supported;
};
$R.Constructor.prototype.createEvent = (function () {
if (doc.createEvent) {
return function (type) {
var event = doc.createEvent("HTMLEvents");
event.initEvent(type, true, false);
$NS.eachKey(this, function (val) {
val.dispatchEvent(event);
});
};
}
if (doc.createEventObject) {
return function (type) {
var event = doc.createEventObject();
event.eventType = type;
$NS.eachKey(this, function (val) {
val.fireEvent('on' + type, event);
});
};
}
return $R.functionNull;
}());
You can add a body to your function and have it return undefined:
$R.functionNull = function() {
// Events not supported.
return undefined;
};
This keeps the same semantics as a "truly empty" function, and should satisfy JSLint.
Use the lambda expression:
$R.functionNull = () => void 0;
For me this works best:
emptyFunction = Function();
console.log(emptyFunction); // logs 'ƒ anonymous() {}'
console.log(emptyFunction()); // logs 'undefined'
It's so short that I wouldn't even assign it to a variable (of course you can also use a constant-like variable "EF" or so, that's even shorter and doesn't need the additioal "()" brackets). Just use "Function()" anywhere you need a truly empty function, that doesn't even have a name, not even when you assign it to a variable, and that's the small behaviour difference between my solution and Frédéric's:
// --- Frédéric ---
emptyFunction = function() {
return undefined;
}
console.log(emptyFunction.name); // logs '"emptyFunction"'
// --- me ---
emptyFunction = Function();
console.log(emptyFunction.name); // logs '""' (or '"anonymous"' in chrome, to be fair)
What about returning
return () => undefined;
instead of
return $R.functionNull;

javascript: Error passing back object

I get an error passing back an object from function to calling function.
What am I doing wrong?
function stStartProcessing()
{
var returnValue = {};
returnValue = srGetNextRecord(); // returnValue is undefined
}
function srGetNextRecord()
{
var returnValue = {};
returnValue.addressToArray = "AAA";
returnValue.sequence = "111";
console.log(returnValue); // this works
return returnValue;
}
There must be a different problem in your code, since what you posted works fine.
The modified code below shows 111. See this DEMO
function stStartProcessing()
{
var returnValue = {};
returnValue = srGetNextRecord(); // returnValue is undefined -- no, it's not
console.log(returnValue.sequence); //shows 111
}
function srGetNextRecord()
{
var returnValue = {};
returnValue.addressToArray = "AAA";
returnValue.sequence = "111";
console.log(returnValue); // this works
return returnValue;
}
stStartProcessing();
On a separate note, when writing JavaScript, please get into the habit of putting your opening braces on the same line—always. For what you have above it won't make a difference, but if you ever do this:
function foo()
{
return
{
x: 1,
y: 2
};
}
horrible things will happen—a semicolon will be inserted after the word return, thereby killing your return value, and causing a script error.

javascript jquery function is this somehow wrong?

function rebuildJSONObject(){
$.getJSON('services.json', function(data) {
//stof start
var input = data;
var output = { myservices: [] };
for (var key in input) {
if (input.hasOwnProperty(key)) {
for (var i = 0, hostsinfo = input[key].hostsinfo; i < hostsinfo.length; i++) {
output.myservices.push({
'nametag': key,
'hostidn': hostsinfo[i]['hostidn'],
'details': hostsinfo[i]['details'],
'currstatus': hostsinfo[i]['currstatus'],
'currstatusclass': hostsinfo[i]['currstatusclass']
});
}
}
}
//stof end
return output;
});
}
//setting it for use later in the script
var serviceJSONObject = rebuildJSONObject();
I know the stuff going on in the function is working properly cause if I apply it to a click event it works charming. However I would rather load the JSON object into memory once and work with it client side there after unless saved. My Problem is however anywhere I call "serviceJSONObject" I get an "undefined" error.
So How am I doing this wrong and how would I define a variable like this early in the game so the rest of the script can use said variable.
The issue is that output is returned before the callback function is called. You should be able to save the value to serviceJSONObject by using a closure:
function rebuildJSONObject(serviceJSONObject){
$.getJSON('services.json', function(data) {
//stof start
var input = data;
// Use the serviceJSONObject that is passed into rebuildJSONObject
serviceJSONObject = { myservices: [] };
for (var key in input) {
if (input.hasOwnProperty(key)) {
for (var i = 0, hostsinfo = input[key].hostsinfo; i < hostsinfo.length; i++) {
serviceJSONObject.myservices.push({
'nametag': key,
'hostidn': hostsinfo[i]['hostidn'],
'details': hostsinfo[i]['details'],
'currstatus': hostsinfo[i]['currstatus'],
'currstatusclass': hostsinfo[i]['currstatusclass']
});
}
}
}
//stof end
});
}
//setting it for use later in the script
var serviceJSONObject;
rebuildJSONObject(serviceJSONObject);
Why not add a cache property to a function that will store the result of the initial output (loaded via ajax) and returning the saved state to any consecutive call.
function rebuildJSONObject(callback) {
var self = this;
if (typeof self.cache !== 'undefined') {
if (typeof callback === 'function') {
callback(self.cache);
}
return;
}
$.getJSON('services.json', function(data) {
//stof start
var input = data,
output = { myservices: [] };
for (var key in input) {
if (input.hasOwnProperty(key)) {
for (var i = 0, hostsinfo = input[key].hostsinfo; i < hostsinfo.length; i++) {
output.myservices.push({
'nametag': key,
'hostidn': hostsinfo[i]['hostidn'],
'details': hostsinfo[i]['details'],
'currstatus': hostsinfo[i]['currstatus'],
'currstatusclass': hostsinfo[i]['currstatusclass']
});
}
}
}
//stof end
self.cache = output;
if (typeof callback === 'function') {
callback(self.cache);
}
return;
});
}
EDIT: For the first time you will need to call this function asynchronously and supply a callback function, for example
rebuildJSONObject(function(output) {
/*
* Process your output here
*/
console.log(output);
});
Each consecutive time you can again use it synchronously:
console.log(rebuildJSONObject.cache);
There are a couple of problems with this.
The call to getJSON is asynchronous so you need to be careful you don't try to use the results before the call has returned your results.
The way it is at the moment, the results will not be returned to serviceJSONObject. The return output statement is setting the return for the anonymous function, not the return value for rebuildJSONObject, so the results will just disappear. If you want the results to be available elsewhwere in code you will either need to store them in a global variable or access them inside the callback.

Categories

Resources