This is the code, it goes over an object looking for deeply-nested children and supposed to stop once there are no more children or it exceeds ALLOWED_NESTING_DEPTH.
The console.log seems to run only once, although I do get the correct number.
const ALLOWED_NESTING_DEPTH = 4
function traverseChildren (childrenIdList, parentsNesting = 0) {
parentsNesting++
if (parentsNesting > ALLOWED_NESTING_DEPTH || _.isEmpty(childrenIdList)) {
console.log(parentsNesting) // Why does this show only once even if there are many levels of nesting?
return parentsNesting
}
let children = childrenIdList.map(child => _.find(tree.items, { id: child }))
let allChildren = []
if (!_.isEmpty(children)) {
allChildren = _.flattenDeep(children.map(child => child.children))
}
return traverseChildren(allChildren, parentsNesting)
}
traverseChildren(someChild)
When you enter this block:
if (parentsNesting > ALLOWED_NESTING_DEPTH || _.isEmpty(childrenIdList)) {
console.log(parentsNesting) // Why does this show only once even if there are many levels of nesting?
return parentsNesting
}
... you are returning an object and not calling the function again. In other words, this is your terminating case. You only come through here once, so you only see one console.log.
Related
I hope to be clear in my first question here.
I was trying to perform a sort on the exhibitors array and while troubleshooting I got these confusing logs in the console.
When I am console logging an array it shows the list of objects but logging an element of any index of the same array shows undefined.
I am aware of the nested subscriptions, there is an API flaw that will be fixed.
I got half an answer from work, I am trying to sort that array before the subscription ends, asynchronous behaviour I presume. So if I move the sorting method into the subscription after the exhibitor list gets built, the problem is solved.
One question remains, why the confusing logs in the console. If the subscription didn't end, shouldn't we get undefined for all? This made it so hard to debug.
Bellow is the context, method in an Angular component.
listExhibitors() {
this.listEx$ = this.exhibitorService.list().subscribe((res) => {
this.allExhibitors = res;
this.exhibitors = [];
this.exhibitorsFiltered = []
this.userId = this.auth.getUserId();
for (let exhib of this.allExhibitors) {
this.exhibitorService.get(exhib.idx).subscribe((res2: any) => {
let members = res2.members
members.forEach(member => {
if (member.userIdx === this.userId) {
console.log('Pushin user..')
this.exhibitors.push(exhib)
}
})
});
}
console.log(this.exhibitors) // logs in the console an array of objects
console.log(typeof (this.exhibitors)) // Object
console.log(this.exhibitors[0]) // undefined
this.exhibitorsFiltered = this.exhibitors.sort(function (a, b) {
return ((a.name < b.name) ? -1 : ((a.name > b.name) ? 1 : 0));
})
console.log(this.exhibitorsFiltered) // logs in the console same array of objects
console.log(this.exhibitorsFiltered[0]) // undefined
})
}
First five logs are from the snippet in that order, the last one is console.log(typeof (this.exhibitorService.list()))
more descriptive logs
this is the code that works:
listExhibitors() {
this.listEx$ = this.exhibitorService.list().subscribe((res) => {
this.allExhibitors = res;
console.log(res)
this.exhibitors = [];
this.exhibitorsFiltered = []
this.userId = this.auth.getUserId();
for (let exhib of this.allExhibitors) {
this.exhibitorService.get(exhib.idx).subscribe((res2: any) => {
let members = res2.members
members.forEach(member => {
if (member.userIdx === this.userId) {
this.exhibitors.push(exhib)
}
})
this.exhibitorsFiltered = this.exhibitors.sort((a, b) => {
//handle null cases
if (!a && !b) { return 0; }
if (!a) { return 1; }
if (!b) { return -1; }
//both not null do actual compare
return a.name.localeCompare(b.name, navigator.languages[0]
|| navigator.language, { numeric: true, ignorePunctuation: true });
});
});
}
});
}
The callback passed into subscribe is likely happening on a different process tick -- that's generally how callbacks happen in node.
console.log(this.exhibitors) is happening before any of the elements within the subscribe function get added.
If you were to put a console.log each time you push, something like:
if (member.userIdx === this.userId) {
console.log('Pushing user');
this.exhibitors.push(exhib)
}
You'll likely see that you get the empty array first, followed by the pushes.
The way to fix that will depend on what exactly you're trying to do, but it should be the case that your array is being populated. You might see it if you were to do:
setTimeout(() => {
console.log(this.exhibitors);
}, 5000);
I'm new to javascript, and scratching my head over this issue:
I used to use the following to grab a bunch of product titles from the page:
CODE 1:
var productTitles = document.querySelectorAll(".product-title");
Then I used the following code to ad a list of these titles to a form's textarea field:
CODE 2:
var MyTextAreaField = document.querySelector("#my-textarea-field");
MyTextAreaField.value = [...productTitles].map(el=>el.textContent).filter(txt=>txt !== 'None').join('\n');
The above worked great, however, I just changed CODE 1 to be a function instead (in order to conditionally return product titles)
The below code is just a rough example of what it looks like:
CODE 3:
var productTitleOne = document.querySelectorAll(".product-title1");
var productTitleTwo = document.querySelectorAll(".product-title2");
var productTitleThree = document.querySelectorAll(".product-title2");
function createProductTitles() {
if (productTypeOne == null) {
return productTitleOne.textContent;
} else if (productTypeTwo == "None") {
return productTitleTwo.textContent;
} else {
return productTitleThree.getAttribute("data-qty") + 'x' + selectionItem.textContent ;
}
}
Problem is, now code 2 no longer works, because it is no longer an Array
So, I tried doing this (adding my function to a variable):
var productTitles = createProductTitles();
But the following still doesn't work, because it's still not really an array
MyTextAreaField.value = [...productTitles].map(el=>el.textContent).filter(txt=>txt !== 'None').join('\n');
So how do I get the result of my function to post to my textarea field?
The problem is the value you're returning on createProductTitles in the Code 1 you're using the array returned by var productTitles = document.querySelectorAll(".product-title"); in the Code 3 you're returning the textContent of that selector, i.e. return productTitleOne.textContent;.
It's important to make a distinction between these two codes because they're sending different data types one is returning an array and the other is returning a string.
So, you need to change your function to return the same the result of the querySelectorAll function
var productTitleOne = document.querySelectorAll(".product-title1");
var productTitleTwo = document.querySelectorAll(".product-title2");
var productTitleThree = document.querySelectorAll(".product-title2");
function createProductTitles() {
if (productTypeOne == null) {
return productTitleOne;
} else if (productTypeTwo == "None") {
return productTitleTwo;
} else {
return productTitleThree
}
}
and then use your function
var productTitles = createProductTitles();
Your function in CODE 3 needs to change.
document.querySelectorAll() returns a NodeList (similar to an array). So it's no good to then try and access the textContent property or call getAttribute() as you do in that function, both of which should instead be called (if desired) on the individual Nodes in the NodeList.
You can modify that function so that the calls you have made take place on the individual Nodes using the spread operator and map function, similarly to how you did in CODE 2:
function createProductTitles() {
if (productTypeOne == null) {
return [ ...productTitleOne].map( productTitles => productTitles.textContent );
} else if (productTypeTwo == "None") {
return [ ...productTitleTwo].map( productTitles => productTitles.textContent );
} else {
return [...productTitleThree].map( productTitles => productTitles.getAttribute("data-qty") + 'x' + selectionItem.textContent );
}
}
This function will return an array of string values that you can do with as you wish, such as:
createProductTitles().filter(txt=>txt !== 'None').join('\n');
Im making a program that takes some code via parameter, and transform the code adding some console.logs to the code. This is the program:
const escodegen = require('escodegen');
const espree = require('espree');
const estraverse = require('estraverse');
function addLogging(code) {
const ast = espree.parse(code);
estraverse.traverse(ast, {
enter: function(node, parent) {
if (node.type === 'FunctionDeclaration' ||
node.type === 'FunctionExpression') {
addBeforeCode(node);
}
}
});
return escodegen.generate(ast);
}
function addBeforeCode(node) {
const name = node.id ? node.id.name : '<anonymous function>';
const beforeCode = "console.log('Entering " + name + "()');";
const beforeNodes = espree.parse(beforeCode).body;
node.body.body = beforeNodes.concat(node.body.body);
}
So if we pass this code to the function:
console.log(addLogging(`
function foo(a, b) {
var x = 'blah';
var y = (function () {
return 3;
})();
}
foo(1, 'wut', 3);
`));
This is the output of this program:
function foo(a, b) {
console.log('Entering foo()');
var x = 'blah';
var y = function () {
console.log('Entering <anonymous function>()');
return 3;
}();
}
foo(1, 'wut', 3);
And this is the AST (Abstract Syntax Tree) for that last function passed to addLoggin:
https://astexplorer.net/#/gist/b5826862c47dfb7dbb54cec15079b430/latest
So i wanted to add more information to the console logs like for example the line number we are on. As far as i know, in the ast, the node has a value caled 'start' and 'end' which indicates in which character that node starts and where it ends. How can i use this to get the line number we are on? Seems pretty confusing to me to be honest. I was thinking about doing a split of the file by "\n", so that way i have the total line numbers, but then how can i know i which one im on?
Thank you in advance.
Your idea is fine. First find the offsets in the original code where each line starts. Then compare the start index of the node with those collected indexes to determine the line number.
I will assume here that you want the reported line number to refer to the original code, not the code as it is returned by your function.
So from bottom up, make the following changes. First expect the line number as argument to addBeforeCode:
function addBeforeCode(node, lineNum) {
const name = node.id ? node.id.name : '<anonymous function>';
const beforeCode = `console.log("${lineNum}: Entering ${name}()");`;
const beforeNodes = espree.parse(beforeCode).body;
node.body.body = beforeNodes.concat(node.body.body);
}
Define a function to collect the offsets in the original code that correspond to the starts of the lines:
function getLineOffsets(str) {
const regex = /\r?\n/g;
const offsets = [0];
while (regex.exec(str)) offsets.push(regex.lastIndex);
offsets.push(str.length);
return offsets;
}
NB: If you have support for matchAll, then the above can be written a bit more concise.
Then use the above in your main function:
function addLogging(code) {
const lineStarts = getLineOffsets(code); // <---
let lineNum = 0; // <---
const ast = espree.parse(code);
estraverse.traverse(ast, {
enter: function(node, parent) {
if (node.type === 'FunctionDeclaration' ||
node.type === 'FunctionExpression') {
// Look for the corresponding line number in the source code:
while (lineStarts[lineNum] < node.body.body[0].start) lineNum++;
// Actually we now went one line too far, so pass one less:
addBeforeCode(node, lineNum-1);
}
}
});
return escodegen.generate(ast);
}
Unrelated to your question, but be aware that functions can be arrow functions, which have an expression syntax. So they would not have a block, and you would not be able to inject a console.log in the same way. You might want to make your code capable to deal with that, or alternatively, to skip over those.
I am working through a code challenge and I need to return a string into a variable if the guess passed in attemptAnswer(guess) matches the answer property's value. My test is failing currently saying that the variable response is undefined.
Is this some sort of binding issue?
...curious how this problem could be resolved.
Thank you!
class Sphinx {
constructor() {
this.name = null;
this.riddles = [];
}
collectRiddle(riddle) {
this.riddles.push(riddle);
if (this.riddles.length > 3) { this.riddles.shift() };
}
attemptAnswer(guess) {
this.riddles.forEach( (element, index) => {
if (guess === element.answer) {
this.riddles.splice(index, 1);
return "That wasn't that hard, I bet you don't get the next one."
};
})
}
}
//test
const response = sphinx.attemptAnswer('short');
assert.equal(response, 'That wasn\'t that hard, I bet you don\'t get the next one');
When you return in attemptAnswer() you're actually retuning to the inner forEach callback function you defined: (element, index) => {..., not the outer attemptAnswer() method.
Instead of immediately returning within your forEach loop, you can set a variable outside this loop called result, and then return the result once your forEach loop is complete.
Also, currently, you're not creating a new instance of Sphinx, which means you don't have an object which can call the attemptAnswer() method. To fix this add new Sphinx() to create a new Sphinx object.
See example below:
class Sphinx {
constructor() {
this.name = null;
this.riddles = [{"answer":"short"}];
}
collectRiddle(riddle) {
this.riddles.push(riddle);
if (this.riddles.length > 3) {
this.riddles.shift()
};
}
attemptAnswer(guess) {
let res = "";
this.riddles.forEach((element, index) => {
if (guess === element.answer && !res) {
// no need for splice as it will skip an entry
res = "That wasn't that hard, I bet you don't get the next one.";
};
})
return res;
}
}
const response = new Sphinx();
response.collectRiddle({"answer":"short"});
console.log(response.attemptAnswer('short'));
you're never calling collectRiddle so this.riddles is always [] and the forEach block is never entered, therefore, not returning anything, so, the return value is undefined
you should have a variable called found right before the loop, if you find a match, set it to truethen return the string depending on the found variable :
note : the string inside the function is different from the one you're comparing it to (it has backslashes and ends with a dot) so the test will always be falsy
class Sphinx {
constructor() {
this.name = null;
this.riddles = [];
}
collectRiddle(riddle) {
this.riddles.push(riddle);
if (this.riddles.length > 3) {
this.riddles.shift()
};
}
attemptAnswer(guess) {
var found = false;
this.riddles.forEach((element, index) => {
if (guess === element.answer) {
found = true;
}
})
return found ? "Woohoo" : "That wasn't that hard, I bet you don't get the next one."
}
}
//test
const s = new Sphinx();
const response = s.attemptAnswer('short');
console.log(response === `That wasn't that hard, I bet you don't get the next one.`);
I assume you already did const sphynx = new Sphynx().
attemptAnswer() doesn't return anything, in Javascript, if you don't return anything, you basically return undefined. So it is normal that response is undefined.
In your case, I would use for-loop, instead of forEach.
attemptAnswer(guess) {
for (let i = 0; i < this.riddles.length; i++) {
if (guess === this.riddles[i].answer) {
this.riddles.splice(index, 1);
return "That wasn't that hard, I bet you don't get the next one.";
}
}
return "Not found";
}
Using .splice() inside forEach is not recommended
using forEach, will go through all the items inside the array, even if you already found your answer.
I had a function that was initially like this, and worked:
productGroupPages(): PDF.Page[] {
const descriptions = {};
this.areas.forEach(area => descriptions[area.id] = area.description);
return this.json.engagementAreas.map(area => {
return this.productGroupPage(area, descriptions[area.id]);
});
}
Basically everything isnt wrapped in the for each.
I had to alter to the function as i wanted to go through a for each, to then use an if statement so i would only return values that actually contained a certain value, the result is that my return statement is too early, I cant move it to the end because of the for loop and im struggling to find out how I can get past this,
this new function:
productGroupPages(): PDF.Page[] {
const descriptions = {};
let pages = {};
console.log('this .json' + JSON.stringify(this.json))
this.json.engagementAreas.forEach(area => {
descriptions[area.id] = area.description
if (area.engagementTypes.length !== 0) {
return this.json.engagementAreas.map(area => {
return this.productGroupPage(area, descriptions[area.id]);
});
}
})
}
I tried creating a variable, an array or object and equaling that to the return value and then returning that near the end of the scope but this wouldnt let me it said the return type was wrong.
Any help would be greatly appreciated.
I think your initial code, with the forEach and map separated was very clean!
The problem with your new code, is that using return inside .forEach() does not make sense in javascript ;) Whenever you return from .forEach(), javascript just ignores the result:
let result = [1,2,3].forEach(x => {
return x * 2;
});
result // undefined
I think you wanted to only return some area-s, which you can do with array.filter() like this:
productGroupPages(): PDF.Page[] {
const descriptions = {};
this.areas.forEach(area => descriptions[area.id] = area.description);
return this.json.engagementAreas
.filter(area => area.engagementTypes.length !== 0)
.map(area => {
return this.productGroupPage(area, descriptions[area.id]);
});
}
I hope that is actually what you meant to do, and if so, I hope this works :)