Evaluating JavaScript expression with a worker - javascript

I need to evaluate JavaScript expressions, in a browser, Chrome. To make it safe, I use a Blob and a Worker running my evaluator, until it posts back the result of a timeout cancels the wait. This is working fine. I also need to support an environment for my JavaScript. I do this as below:
function evalWorker () {
let postResponse = function(expr, ...) {
let presets = `var EnvObject = {};
EnvObject.platform = "Chrome";
EnvObject.pasteboard = "${clipboard}";
EnvObject.baseDate = new Date();
...
EnvObject._output = "";
EnvObject.appendOutput = (str) => {EnvObject._output += str; };
`
postMessage(eval(presets + expr));
};
onmessage = function(e) {
postResponse(e.data['expression'], e.data['clipboard'], ...);
}
}
My problem is that if _output is not empty, I need to return that - _output instead of the evaluated expression, as in
EnvObject.appendOutput('hello');
var a = 0;
++a;
Should return hello; while without appendOutput, it should return 1.
How would I go about something like this?

#Bergi had the right idea with pushing the scope out. The below works.
function evalWorker () {
let postResponse = function(expr, TextExpander) {
let result = eval(expr);
if (EnvObject._output && EnvObject._output.length) {
postMessage(EnvObject._output);
} else {
postMessage(result);
}
};
onmessage = function(e) {
var EnvObject = {};
EnvObject.platform = "Chrome";
EnvObject.pasteboardText = e.data['clipboard'];
...
EnvObject._output = "";
EnvObject.appendOutput = function(str) {EnvObject._output += str; };
postResponse(e.data['expression'], EnvObject);
}
}

Related

Using a JavaScript function to add data to a string that comes from the HTML document, and preserving that data after that function is called again

I wrote an object called TypeWriter2 and then I want to add it a function called type2().
I then call the TypeWriter2 object using a function called init2(), which queries some data from the html document and passes it to the TypeWriter2 object.
The data that init2() is querying from the html document is:
txtElement2 = a div element, that the type2() function will use to display some data.
words2 = the words that are to be displayed in txtElement2, which is "Hello, there... Yoo"
wait2 = an int that will be passed to setTimeout() later on.
The type2() function is meant is meant of add "iiiiii" to "txt2" (an empty string at the beginning), whenever "txt2" ends with 3 consecutive dots.
The problem being that after "iiiiii" is added to "txt2" and "setTimeout(() => this.type2(), this.wait2);" is called again, "iiiiii" is being deleted from "txt2".
document.addEventListener('DOMContentLoaded', init2);
const TypeWriter2 = function (txtElement2, words2, wait2 = 3000) {
this.txtElement2 = txtElement2;
this.words2 = words2;
this.wait2 = parseInt(wait2, 10);
this.txt2 = '';
this.type2();
}
TypeWriter2.prototype.type2 = function () {
this.txt2 = this.words2.substring(0, this.txt2.length + 1)
if (this.txt2.substr(this.txt2.length - 3) === "...") {
this.txt2 = this.txt2 + "iiiii"
this.txtElement2.innerHTML = `<span class="intro-text">${this.txt2}</span>`;
} else {
this.txtElement2.innerHTML = `<span class="intro-text">${this.txt2}</span>`;
}
setTimeout(() => this.type2(), this.wait2);
}
function init2() {
const txtElement2 = document.querySelector('.intro-text');
const words2 = txtElement2.getAttribute('hello-txt');
const wait2 = txtElement2.getAttribute("data2-wait");
new TypeWriter2(txtElement2, words2, wait2);
}
Thanks in advance!
I was unable to reproduce the bug as described using the posted code, but in all likelihood you can resolve the problem by changing your else statement to an else if such that the type2 method stops being called as soon as all of the text in the "hello-txt" attribute has been added to txtElement2.innerHTML
Attempted repro case: https://jsbin.com/wovatit/1/edit?html,js,output
document.addEventListener('DOMContentLoaded', init2);
const TypeWriter2 = function (txtElement2, words2, wait2 = 3000) {
this.txtElement2 = txtElement2;
this.words2 = words2;
this.wait2 = parseInt(wait2, 10);
this.txt2 = '';
this.type2();
}
TypeWriter2.prototype.type2 = function () {
console.log('called');
this.txt2 = this.words2.substring(0, this.txt2.length + 1)
if (this.txt2.substr(this.txt2.length - 3) === "...") {
this.txt2 = this.txt2 + "iiiii"
this.txtElement2.innerHTML = `<span class="intro-text">${this.txt2}</span>`;
console.log("finished")
} else if(this.txt2.length <= this.words2.length){
this.txtElement2.innerHTML = `<span class="intro-text">${this.txt2}</span>`;
setTimeout(() => this.type2(), this.wait2);
} else{
console.log("finsished")
}
}
function init2() {
const txtElement2 = document.querySelector('.intro-text');
const words2 = txtElement2.getAttribute('hello-txt');
const wait2 = txtElement2.getAttribute("data2-wait");
new TypeWriter2(txtElement2, words2, wait2);
}

How to stop executing Javascript function containing infinite loop after some time

Suppose I have following piece of code that contains an infinite loop:
function infiniteLoop() {
while(true) {
//do something, eg.
document.getElementById("someID").innerHTML = "Blah";
}
}
If we execute this code in an online compiler, browser will crash. I want to prevent that from happening. So I tried following code following this answer:
function willNotCrash() {
myVar = setInterval(infiniteLoop, 5000);
setTimeout(function(){
clearInterval(myVar);
}, 4000);
}
This code doesn't make the browser to crash, because I am stopping the execution before infiniteLoop() gets called by clearInterval(myVar).
My question is how do I stop executing such functions if they don't response within some period of time (eg. after 5 seconds or before the browser is crashed).
For example, if we copy paste following java code in https://www.compilejava.net/
public class HelloWorld {
public static void main(String[] args) {
while(true) {
System.out.println("Blah");
}
}
}
we get a nice output saying,
Script was taking longer than 5 seconds to execute so it was killed.
Here is my current code: http://js.do/code/106546
This is a bit tricky but perfectly doable. You need to tokenize the script and then rebuild it but insert a counter increment in every loop and function call. If the counter goes above some threshold, then bomb out. I did it here: https://littleminigames.com/
You can see the source at https://bitbucket.org/cskilbeck/littleminigames/src
The interesting bits are in wrapper.js (https://bitbucket.org/cskilbeck/littleminigames/src/ac29d0d0787abe93c75b88520050a6792c04d34d/public_html/static/js/wrapper.js?at=master&fileviewer=file-view-default)
Google escodegen, estraverse and esprima
I relied heavily on this: https://github.com/CodeCosmos/codecosmos/blob/master/www/js/sandbox.js
wrapper.js, as requested:
// Don't obfuscate this file! We depend on the toString() of functions!
// this was all nicked from https://github.com/CodeCosmos/codecosmos/blob/master/www/js/sandbox.js
(function(mainApp) {
'use strict';
var esprima = window.esprima,
estraverse = window.estraverse,
escodegen = window.escodegen,
errors = [],
eng,
Syntax = estraverse.Syntax;
// This implements the jankiest possible "source map", where we keep an array
// of [generatedLine, knownSourceLine]. Seems to essentially work.
function SourceNode(line, col, _sourceMap, generated) {
this.line = line;
this.col = col;
this.generated = generated;
}
SourceNode.prototype.toStringWithSourceMap = function toStringWithSourceMap() {
var code = [];
var mapLines = {};
var map = [];
// assumes that wrapCode adds two lines
var line = 3;
var lastMapLine = null;
function walk(node) {
if (typeof(node) === "string") {
if (node) {
code.push(node);
var matches = node.match(/\n/g);
if (matches !== null) {
line += matches.length;
}
}
} else if (node instanceof SourceNode) {
if (node.line !== null) {
if (!mapLines[line]) {
map.push([line, node.line]);
mapLines[line] = node.line;
}
}
walk(node.generated);
} else {
node.forEach(walk);
}
}
walk(this);
return {
code: code.join(''),
map: map
};
};
SourceNode.prototype.toString = function toString() {
return this.toStringWithSourceMap().code;
};
// This is used by escodegen
window.sourceMap = {
SourceNode: SourceNode
};
// TODO (chs): add in all the things that need to be masked
function runWrapper($userCode, __sys) {
var clear = __sys.clear,
setpixel = __sys.setpixel,
rectangle = __sys.rectangle,
box = __sys.box,
line = __sys.line,
getpixel = __sys.getpixel,
getpixeli = __sys.getpixeli,
keypress = __sys.keypress,
keyrelease = __sys.keyrelease,
keyheld = __sys.keyheld,
reset = __sys.reset;
__sys.userFunction = __sys.catchErrors($userCode);
}
function extractCode(fn) {
var code = fn.toString();
return code.substring(code.indexOf('{') + 1, code.lastIndexOf('}'));
}
function makeOneLine(code) {
return code.replace(/(\/\/[^\n]+|\n\s|\r\n\s*)/g, '');
}
var runTemplate = makeOneLine(extractCode(runWrapper));
function wrapCode(code, template, functionName, postCode) {
// avoid interpretation of the replacement string by using a fun.
// otherwise mo' $ mo problems.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#Specifying_a_string_as_a_parameter
return ("'use strict';" + template.replace(/\$userCode/, function() {
return 'function ' + functionName + '() {\n' + code + postCode + '\n}';
}));
}
var injectStatement = esprima.parse("if (++__sys.ctr >= __sys.maxctr) throw new Error('Script halted - infinite loop?');").body[0];
var injectElseStatement = esprima.parse("if (++__sys.ctr >= __sys.maxctr) throw new Error('Script halted - infinite loop?'); else ;").body[0];
function CallExpression(callee, args) {
this.callee = callee;
this.arguments = args;
}
CallExpression.prototype.type = Syntax.CallExpression;
function Identifier(name) {
this.name = name;
}
Identifier.prototype.type = Syntax.Identifier;
function BlockStatement(body) {
this.body = body;
}
BlockStatement.prototype.type = Syntax.BlockStatement;
function ReturnStatement(argument) {
this.argument = argument;
}
ReturnStatement.prototype.type = Syntax.ReturnStatement;
function FunctionExpression(id, params, body) {
this.id = id;
this.params = params;
this.body = body;
this.defaults = [];
this.expression = false;
this.generator = false;
this.rest = null;
}
FunctionExpression.prototype.type = Syntax.FunctionExpression;
function wrapId(node, defaultName) {
if (node.loc) {
var id = (node.id || {
name: null,
loc: null
});
var loc = id.loc || node.loc;
var name = id.name || defaultName;
return new Identifier(name + '$' + loc.start.line);
} else {
return node.id;
}
}
function instrumentAST(ast) {
var identifierStack = [];
function pushIdentifier(s) {
identifierStack[identifierStack.length - 1].push(s);
}
function popIdentifierStack() {
identifierStack.pop();
}
function pushIdentifierStack() {
identifierStack.push([]);
}
function peekLastIdentifier() {
var lastStackIdx = identifierStack.length - 1;
if (lastStackIdx >= 0) {
var stack = identifierStack[lastStackIdx];
if (stack.length) {
return stack[stack.length - 1];
}
}
return '';
}
pushIdentifierStack();
return estraverse.replace(ast, {
enter: function enterAST(node) {
switch (node.type) {
case Syntax.VariableDeclarator:
if (node.id.type === Syntax.Identifier) {
pushIdentifier(node.id.name);
}
break;
case Syntax.MemberExpression:
if (node.object.type === Syntax.Identifier) {
var id = node.object.name;
if (node.property.type === Syntax.Identifier) {
id += '__dot__' + node.property.name; // huh? why mangle these?
// console.log(id);
}
pushIdentifier(id);
} else if (node.property.type === Syntax.Identifier) {
pushIdentifier(node.property.name);
}
break;
case Syntax.FunctionDeclaration:
pushIdentifierStack();
break;
case Syntax.FunctionExpression:
pushIdentifierStack();
break;
default:
break;
}
return node;
},
leave: function leaveAST(node) {
switch (node.type) {
case Syntax.DoWhileStatement:
break;
case Syntax.ForStatement:
break;
case Syntax.FunctionDeclaration:
break;
case Syntax.FunctionExpression:
break;
case Syntax.WhileStatement:
break;
default:
return estraverse.SKIP;
}
// modify the BlockStatement in-place to inject the instruction counter
if(node.body.body === undefined) {
// they have used a non-block statement as the body of a function or loop construct
// not allowed for function declarations - should never get here
if(node.type === Syntax.FunctionDeclaration) {
errors.push({
message: "Missing {",
line: node.loc.start.line,
column: node.loc.start.column
});
}
else {
// otherwise insert the test
var newBody = angular.copy(injectElseStatement);
newBody.alternate = node.body;
node.body = newBody;
}
return estraverse.SKIP;
}
node.body.body.unshift(injectStatement);
if (node.type === Syntax.FunctionExpression) {
popIdentifierStack();
// __catchErrors(node)
node.id = wrapId(node, peekLastIdentifier());
return new CallExpression(
new Identifier("__sys.catchErrors"), [node]);
}
if (node.type === Syntax.FunctionDeclaration) {
popIdentifierStack();
// modify the BlockStatement in-place to be
// return __catchErrors(function id() { body });
var funBody = node.body;
node.body = new BlockStatement([
new ReturnStatement(
new CallExpression(
new CallExpression(
new Identifier("__sys.catchErrors"), [new FunctionExpression(
wrapId(node, peekLastIdentifier()), [],
funBody)]), []))
]);
}
return node;
}
});
}
// mainApp.sandbox('var a = 1; function update(frame) { clear(0); }').code
// give it the source code as a string
mainApp.sandbox = function(code) {
var rc = {};
this.errors = [];
try {
this.ast = instrumentAST(esprima.parse(code, { range: true, loc: true }));
this.map = escodegen.generate(this.ast, { sourceMap: true, sourceMapWithCode: true });
this.code = wrapCode(this.map.code, runTemplate, '', ';\n__sys.updateFunction = (typeof update === "function") ? update : null;');
}
catch(e) {
this.errors.push({
message: e.description,
line: e.lineNumber,
column: e.column
});
}
if(this.code) {
this.code = "eng.clientFunction = function(__sys) {" + this.code + "};";
}
};
mainApp.sandbox.prototype.searchMap = function(needle) {
// binary search
var lo = 0;
var hi = this.map.map.length;
var mid, here;
while (true) {
mid = lo + ((hi - lo) >> 1);
here = this.map.map[mid];
if (mid === lo || here[0] === needle) {
return here[1];
} else if (here[0] > needle) {
hi = mid;
} else {
lo = mid;
}
}
};
})(mainApp);
Typically all JavaScript runs in one thread, so it is impossible to run any JavaScript that could stop your loop while your loop is running. Using HTML5 web workers, you can run the infinite loop in a separate thread, and then you can terminate it:
var myWorker = new Worker( '/infinite.js ');
setTimeout( function ( ) {
myWorker.terminate( );
}, 5000 );
However your web worker won't have access to the DOM, so the contents of your infinite loop would need to be different that what you have in your question.
I found exactly what I was looking for in Bergi's comment,
Alternatively, place a if (Date.now() > dateAtStartOfExecution+5000) return; in every loop body.
So now my code looks like:
function infiniteLoop() {
dateAtStartOfExecution = Date.now();
while(true) {
//do something
document.getElementById("someID").innerHTML = "Blah";
if (Date.now() > dateAtStartOfExecution+5000) {
alert("Taking too much time. Killing.");
return;
}
}
}
If I run this code after 5 seconds I will get an alert and the execution will stop. Try this:
http://js.do/code/106565

Is there a way to stop a setInterval without its timerId? [duplicate]

I'm writing an application that utilizes JavaScript timeouts and intervals to update the page. Is there a way to see how many intervals are setup? I want to make sure that I'm not accidentally going to kill the browser by having hundreds of intervals setup.
Is this even an issue?
I don't think there is a way to enumerate active timers, but you could override window.setTimeout and window.clearTimeout and replace them with your own implementations which do some tracking and then call the originals.
window.originalSetTimeout = window.setTimeout;
window.originalClearTimeout = window.clearTimeout;
window.activeTimers = 0;
window.setTimeout = function(func, delay) {
window.activeTimers++;
return window.originalSetTimeout(func, delay);
};
window.clearTimeout = function(timerID) {
window.activeTimers--;
window.originalClearTimeout(timerID);
};
Of course, you might not always call clearTimeout, but this would at least give you some way to track what is happening at runtime.
I made a Chrome DevTools extension that shows all intervals. Cleared ones are greyed out.
setInterval-sniffer
Instead of just have a count of timers, here is an implementation which stores all timerid's into an array. It only shows active timers while the accepted answer only counts calls to setTimeout & clearTimeout.
(function(w) {
var oldST = w.setTimeout;
var oldSI = w.setInterval;
var oldCI = w.clearInterval;
var timers = [];
w.timers = timers;
w.setTimeout = function(fn, delay) {
var id = oldST(function() {
fn && fn();
removeTimer(id);
}, delay);
timers.push(id);
return id;
};
w.setInterval = function(fn, delay) {
var id = oldSI(fn, delay);
timers.push(id);
return id;
};
w.clearInterval = function(id) {
oldCI(id);
removeTimer(id);
};
w.clearTimeout = w.clearInterval;
function removeTimer(id) {
var index = timers.indexOf(id);
if (index >= 0)
timers.splice(index, 1);
}
}(window));
This is how you can get the count of active timers on the page:
timers.length;
This is how you can remove all active timers:
for(var i = timers.length; i--;)
clearInterval(timers[i]);
Known limitations:
You can only pass a function (not a string) to setTimeout with this monkey patch.
The function assumes clearInterval and clearTimeout do the same, which they do but it could change in the future.
Seeing as Paul has only covered setTimeout I thought I would share a counter for setInterval/clearInterval.
window.originalSetInterval = window.setInterval;
window.originalClearInterval = window.clearInterval;
window.activeIntervals = 0;
window.setInterval = function (func, delay)
{
if(func && delay){
window.activeIntervals++;
}
return window.originalSetInterval(func,delay);
};
window.clearInterval = function (intervalId)
{
// JQuery sometimes hands in true which doesn't count
if(intervalId !== true){
window.activeIntervals--;
}
return window.originalClearInterval(intervalId);
};
We've just published a package solving this exact issue.
npm install time-events-manager
With that, you can view and manage them via timeoutCollection object (and javascript's intervals viaintervalCollection object).
timeoutCollection.getScheduled();
timeoutCollection.getCompleted();
timeoutCollection.getAll();
I just needed something like this and this is what I've put together:
window.setInterval = function (window, setInterval) {
if (!window.timers) {
window.timers = {};
}
if (!window.timers.intervals) {
window.timers.intervals = {};
}
if (!window.timers.intervals.active) {
window.timers.intervals.active = {};
}
return function (func, interval) {
var id = setInterval(func, interval);
window.timers.intervals.active[id] = func;
return id;
}
}(window, window.setInterval);
window.clearInterval = function (window, clearInterval) {
if (!window.timers) {
window.timers = {};
}
if (!window.timers.intervals) {
window.timers.intervals = {};
}
if (!window.timers.intervals.inactive) {
window.timers.intervals.inactive = {};
}
return function (id) {
if (window.timers.intervals.active && window.timers.intervals.active[id]) {
window.timers.intervals.inactive[id] = window.timers.intervals.active[id];
clearInterval(id);
delete window.timers.intervals.active[id];
}
}
}(window, window.clearInterval);
This records the interval ids along with their functions, and also keeps track of their status (active/inactive).
Based on #Alessio's answer. Below is my version. Has a bit more functionality for logging and inspection.
Here is some boilerplate that you can alter to utilize your own frameworks:
var s$ = function (s){return new String(s)}
var _w=window
_w.q$ = {
getMachineTimeMS: function(){
var d = new Date(), ms = d.getMilliseconds()
var a = [d.getHours(), d.getMinutes(), d.getSeconds(), '-', ms<10?'00' + s$(ms):ms<100?'0'+s$(ms):ms]
return a.join('')
}
,getCaller: function(opts){
return "(implement this)"
}
}
Here is the main code:
_w.setTimeout = function (orig_setTimeout) {
var t=(_w._Timers = _w._Timers||{})
var d=(t.Timeouts = t.Timeouts||{})
d.Active = d.Active||{}
t.z_to_id_idx = t.z_to_id_idx||{}
return function (h, n) {
var t = _w._Timers, d = t.Timeouts
var id = orig_setTimeout(h, n), ts = q$.getMachineTimeMS()
var c = q$.getCaller({depth:2})
t.z_to_id_idx[s$(id)] = d.Active[ts] = {sts: ts, id: id, h: h, n: n, scaller: c}
return id;
}
}(_w.setTimeout);
_w.clearTimeout = function (orig_clearTimeout) {
var t=_w._Timers, d = t.Timeouts
d.Inactive = d.Inactive||{}
return function new_clearTimeout(id) {
var t = _w._Timers, d = t.Timeouts, sId = s$(id)
if (!d.Active || !sId in t.z_to_id_idx) return
var r = t.z_to_id_idx[sId]
r.ccaller = q$.getCaller({depth:2})
r.cts = q$.getMachineTimeMS()
d.Inactive[r.ts] = r;
orig_clearTimeout(r.id);
delete d.Active[r.ts]
delete t.z_to_id_idx[sId]
}
}(_w.clearTimeout);
_w.setInterval = function (orig_setInterval) {
var t=(_w._Timers = _w._Timers||{})
var d=(t.Intervals = t.Intervals||{})
d.Active = d.Active||{}
t.z_in_id_idx = t.z_in_id_idx||{}
return function (h, n) {
var t = _w._Timers, d = t.Intervals
var id = orig_setInterval(h, n), ts = q$.getMachineTimeMS()
var c = q$.getCaller({depth:2})
t.z_in_id_idx[s$(id)] = d.Active[ts] = {sts: ts, id: id, h: h, n: n, scaller: c}
return id;
}
}(_w.setInterval);
_w.clearInterval = function (orig_clearInterval) {
var t=_w._Timers, d = t.Intervals
d.Inactive = d.Inactive||{}
return function new_clearInterval(id) {
var t = _w._Timers, d = t.Intervals, sId = s$(id)
if (!d.Active || !sId in t.z_in_id_idx) return
var r = t.z_in_id_idx[sId]
r.ccaller = q$.getCaller({depth:2})
r.cts = q$.getMachineTimeMS()
d.Inactive[r.ts] = r;
orig_clearInterval(r.id);
delete d.Active[r.ts]
delete t.z_in_id_idx[sId]
}
}(_w.clearInterval);
Usage example:
id = setTimeout(()=>{console.log("CALLED")}, 10000)
clearTimeout(id)
setInterval(()=>{console.log("CALLED")}, 1000)
console.table(_w._Timers.Timeouts.Inactive)
The console.table will output a nicely formatted and inspectable table in the JavaScript Console

Javascript Callback in for Loop

My problem is that I'm having a Function A which calls at one point another function, let's call it Function B (getChildContent) and needs the return value of Function B in order to proceed. I know that it's because of Javascripts Asynchronous Nature, and i tried to solve it with a callback. But i can't get it work properly.
FunctionA(){
//some Code.....
else {
for(i in clustertitles) {
if(S(text).contains(clustertitles[i])) {
var parent = {};
parent.ClusterName = clustertitles[i];
parent.Functions = [];
var str = '== ' + clustertitles[i] + ' ==\n* ';
str = S(text).between(str,'.').s;
var caps = parseFunctions(str);
for(y in caps) {
//var content = getChildContent(caps[y]);
getChildContent(caps[y], function(content) { //Function call
var child = {};
child.FunctionName = caps[y];
child.Content = [];
child.Content.push(content);
parent.Functions.push(child);
console.log(content);
});
}}}
}
function getChildContent (capname, callback) {
t = capname.replace(' ', '_');
bot.page(t).complete(function (title, text, date) {
var str = S(text).between('== Kurzbeschreibung ==\n* ', '.').s;
if(str === undefined || str === null || str === '') {
throw new Error('Undefined, Null or Empty!');
}
else {
var content = {};
str = parseTitles(str);
content.Owner = str[0];
content.Aim = str[1];
content.What = str[2];
content.Who = str[3];
content.Steps = str[4];
content.Page = 'some URL';
callback(content);
}
});
}
So in Function A I'm trying to call getChildContent from a for-Loop and pass the current string from caps-array. For each String in caps-array getChildContent() makes a http request over a node.js module and retrieves a string. With this string i'm building an object (content) which is needed in Function A to continue. However the 'console.log(content)' in Function A just prints out the object which is created with the last string in caps-array, but for many times. E.G. if caps-array has 5 entries, i get 5 times the object which is created with the last entry of caps-array.
How can i manage the loop/callback to get every time the right object on my console?
Your loop should call another function that preserves the value of y, something like this:
FunctionA(){
//some Code.....
else {
for(i in clustertitles) {
if(S(text).contains(clustertitles[i])) {
var parent = {};
parent.ClusterName = clustertitles[i];
parent.Functions = [];
var str = '== ' + clustertitles[i] + ' ==\n* ';
str = S(text).between(str,'.').s;
var caps = parseFunctions(str);
for(y in caps) {
yourNewFunction (y, caps, parent);
}}}
}
function yourNewFunction (y, caps, parent) {
getChildContent(caps[y], function(content) { //Function call
var child = {};
child.FunctionName = caps[y];
child.Content = [];
child.Content.push(content);
parent.Functions.push(child);
console.log(content);
});
}
function getChildContent (capname, callback) {
t = capname.replace(' ', '_');
bot.page(t).complete(function (title, text, date) {
var str = S(text).between('== Kurzbeschreibung ==\n* ', '.').s;
if(str === undefined || str === null || str === '') {
throw new Error('Undefined, Null or Empty!');
}
else {
var content = {};
str = parseTitles(str);
content.Owner = str[0];
content.Aim = str[1];
content.What = str[2];
content.Who = str[3];
content.Steps = str[4];
content.Page = 'some URL';
callback(content);
}
});
}
There are 2 ways to do so.
Put the loop inside a function, execute your callback after the loop is done. (Problematic if you are doing async call inside the loop.
function doLoopdiloopStuff() {
for() {
}
callback();
}
The other way, the way i prefer looks like this:
for(var i = 0; i < stuff || function(){ /* here's the callback */ }(), false; i++) {
/* do your loop-di-loop */
}
In another example:
for (var index = 0; index < caps.length || function(){ callbackFunction(); /* This is the callback you are calling */ return false;}(); index++) {
var element = caps[index];
// here comes the code of what you want to do with a single element
}

Debugging a push method in Javascript with an object

I am a beginner at js and have a project due by the end of day. I have to display an array with temps added and have set up an object to hold this array. My problem is that the message won't display and the for statement doesn't increment. When passed through both the var i and count come back undefined. I know there is a lot missing from this code but at this point I have tried to stream line it so that I can debug this issue. The date I will deal with later.
Here is my code:
var temps = [];
function process() {
'use strict';
var lowTemp = document.getElementById('lowTemp').value;
var highTemp = document.getElementById('highTemp').value;
var output = document.getElementById('output');
var inputDate = (new Date()).getTime();
var temp = {
inputDate : inputDate,
lowTemp : lowTemp,
highTemp : highTemp
};
var message = '';
if (lowTemp == null) {
alert ('Please enter a Low Temperature!');
window.location.href = "temps.html";
} else if (highTemp == null) {
alert ('Please enter a High Temperature!');
window.location.href = "temps.html";
} else {
lowTemp = parseFloat(lowTemp, 10);
highTemp = parseFloat(highTemp, 10);
}
if (temp.value) {
temps.push(temp.inputDate, temp.lowTemp, temp.highTemp)
var message = '<h2>Temperature</h2><ol>';
for (var i = 0, count = temps.length; i < count; i++) {
message += '<li>' + temps[i] + '</li>'
}
message += '</ol>';
output.innnerHTML = message;
}
return false;
}
function init() {
'use strict';
document.getElementById('theForm').onsubmit = process;
}
window.onload = init;
Here is my new code:
var temps = [];
function process() {
'use strict';
var lowTemp = document.getElementById('lowTemp').value;
var highTemp = document.getElementById('highTemp').value;
var output = document.getElementById('output');
var inputDate = (new Date()).getTime();
var temp = {
inputDate : inputDate,
lowTemp : lowTemp,
highTemp : highTemp
};
var message = '';
if (lowTemp == null) {
alert ('Please enter a Low Temperature!');
window.location.href = "temps.html";
} else if (highTemp == null) {
alert ('Please enter a High Temperature!');
window.location.href = "temps.html";
} else {
lowTemp = parseFloat(lowTemp, 10);
highTemp = parseFloat(highTemp, 10);
}
if (temp.value) {
temps.push(temp.inputDate, temp.lowTemp, temp.highTemp)
var message = '<h2>Temperature</h2><ol>';
for (var i = 0, count = temps.length; i < count; i++) {
message += '<li>' + temps[i] + '</li>'
}
message += '</ol>';
output.innnerHTML = message;
}
return false;
}
function init() {
'use strict';
document.getElementById('theForm').onsubmit = process;
}
window.onload = init;
There are some big issues with your code:
You should never compare anything to NaN directly. The correct comparison should be:
if (isNaN(lowTemp)) {
You're using curly braces when not needed. You should remove both curly braces:
{window.location.href = "temps.html";}
The function parseFloat expects only one parameter: the string to be converted. You're probably confusing it to parseInt which expects both the string and the radix of the conversion.
You're using the temp's property value, but you have never setted it, so, the condition where you check if it exists will always return false, and the push method that you want to debug will never be called, since it's inside that if statement.
Finally, you're closing a li tag at the end, but you have never opened it. You should probably be closing the ol tag you have opened in the begining.
The rest of your code seems pretty OK for me.
Talking about debugging, you should read the Google Chrome's Debugging Javascript Tutorial.

Categories

Resources