Difference in syntax of a function that I cannot understand - javascript

I was writing a function for a Breadth first search alogrithm. I would have come up with the following code
traverseBF() {
const results = []
const queue = [this.root]
while (queue.length) {
let node = queue.shift()
if (node.children) {
queue.push(...node.children)
}
results.push(node)
}
return results
}
However, the solution was written somewhat differently as
traverseBF(fn) {
const queue = [this.root]
while (queue.length) {
let node = queue.shift()
if (node.children) {
queue.push(...node.children)
}
fn(node)
}
}
I cannot explain what fn(node) purpose is or how it returns the correct result. Is this some kind of recursive call? How are the two solutions different?

Instead of returning results like you are, they're allowing the user to pass in a function that gets passed each node as it's traversed.
Try for example:
obj.traverseBF(node => console.log(node))
Or just
obj.traverseBF(console.log)
Should work as well. console.log is given each node to use as it's found.
This is arguably more general purpose. If the tree was large, accumulating all the results in a list may waste memory if the user doesn't need all the results at once.
If the user wanted to accumulate a list though, they could pass in a function that appends to a list that the function closes over:
nodes = [];
obj.traverseBF(node => nodes.push(node))
console.log(nodes)

Related

How to get returned values from my listener's callback ES6 way

I made an input that let me filter a table of softwares.
<input type="text" id="softwares-search" class="form-control" aria-label="Input de recherche" aria-describedby="softwares-search">
Then in javascript my filter work well if I console.log(....)
But when I replace it with a return, nothing is returned. I think it is due to my var affectation through the event listener :
const maxwell = () => {
search = document.querySelector('#softwares-search').value;
return softwares.filter(row => row.name.includes(search) || row.description.includes(search));
}
const softwaresSearch = document.querySelector('#softwares-search');
if (softwaresSearch) {
var results = softwaresSearch.addEventListener('keyup', maxwell)
console.log(results);
}
Thank all
EDIT 1 :
I was so angry, so blind, I had S#!t in my eyes, no need to use a global :(
const softwaresSearch = document.getElementById('softwares-search');
if (softwaresSearch) {
softwaresSearch.addEventListener('keyup', (e) => {
search = document.getElementById('softwares-search').value;
var filtredSoftwares = softwares.filter(e => e.name.includes(search) || e.description.includes(search) );
renderTable(filtredSoftwares);
});
}
const renderTable = (softwares) => {
Object.values(softwares).forEach(value=>{
console.log(value);
});
// Todo build HTML table
}
Instead of returning I think you just need to replace the current array like this
const maxwell = () => {
search = document.querySelector('#softwares-search').value;
softwares = softwares.filter(row => row.name.includes(search) || row.description.includes(search));
}
And results is not needed:
const softwaresSearch = document.querySelector('#softwares-search');
if (softwaresSearch) {
softwaresSearch.addEventListener('keyup', maxwell)
}
As far as I know, softwareSearch.addEventListener won't return anything, since that is an event listener, and does not return any value. It simply executes the function passed in the 2nd parameter. You could try doing this instead
softwaresSearch.addEventListener('keyup', () => {
var results = maxwell();
console.log(results);
});
What this would do is that, it would call your maxwell function when the keyup event, since that is what it looks you are trying to do.
Please share all relevant code before posting a question, this code includes the variable "softwares" that exist outside what is visible to us.
Additionally, there are some issues with your code.
I don't understand your naming of the maxwell function. You should name functions as verbs, not anything else. A function is a machine that is doing something, and possibly returning something. It should be named to what it is doing.
On the second line, you say "search = ... ", but you didn't declare it as a variable.
You are returning something based on a value that isn't validated ('search' can be either undefined or a string value in this case), hence, your return will most likely just return undefined and not any value at all.
Your function can possibly not return anything at all since you are returning something within your if-statement. You can use a closure to always return something.
I would also suggest passing a search string as a variable to your function that should return a list based on the search query. Getting in the habit of short, concise functions with expected inputs/outputs, will make your code more readable and less error-prone and less likely to produce unwanted side-effects.
I don't know the rest of your code, but I don't recommend assigning variables in the global scope. Your "maxwell", "softwareSearch" variables both exist in the global space, unless you have wrapped them in another function block already (such as jquerys $(document).ready(() => { ...everything here is scoped })
You are getting the same element in two different places in your code.
Here is an updated code sample, but I can't test it since I don't know the rest of your code.
/*
* Executing the whole thing in this IIFE will make all variables declared inside here scoped to this block only,
* thus they can't interfere with other code you may write
*/
(() => {
const listOfSoftwares = softwares; // --- get your softwares variable here somehow, I don't know where "software" comes from.
// input element
const search = document.querySelector('#softwares-search');
/**
* Filter search results
* #param {string} query Search query
* #returns {Array} The results array
*/
const filterSoftwareSearchResults = (query) => {
let results = [];
results = listOfSoftwares.filter(software => software.description.includes(query) || software.title.includes(query))
// Verify
console.log(results);
// Should return array of results, even if empty
return results;
}
if (search) {
search.addEventListener('keyup', () => {
filterSoftwareSearchResults(search.value)
})
}
})()
The addEventListener function always returns undefined, so your results variable is undefined.
Returning from the callback function (maxwell) is also of no use.
You either need to do something with the data inside of your callback, or maybe pass the data to a global variable.

Passed-in Values Not Available at Run-time of Function in Angular App

I realize there is something I'm missing in terms of how and specifically when the products of certain functions are available in JavaScript.
In my Angular app, in order to get a user's initials, I am parsing data being returned from the API, and retrieving the first letter of the firstName, as well as the first letter of lastName in two different functions. These two functions are working as expected, and I can see the correct results in the console:
getFirstNameFirstLetter() {
if (this.authenticationService.isAuthenticated()) {
const userObj = JSON.parse(sessionStorage.getItem('currentUser'));
const userInfo = userObj.data;
const firstName = userInfo.name.first;
const firstNameFirstLetter = firstName.trim().charAt(0);
console.log(firstNameFirstLetter);
return firstNameFirstLetter;
}
}
getLastNameFirstLetter() {
if (this.authenticationService.isAuthenticated()) {
const userObj = JSON.parse(sessionStorage.getItem('currentUser'));
const userInfo = userObj.data;
const lastName = userInfo.name.last;
const lastNameFirstLetter = lastName.trim().charAt(0);
console.log(lastNameFirstLetter);
return lastNameFirstLetter;
}
}
Now comes the part I'm not fully understanding. When I then pass the returned values of these two functions, in order to get the initials, like this:
getInitials(firstNameFirstLetter, lastNameFirstLetter) {
if (this.authenticationService.isAuthenticated()) {
if (!this.firstNameFirstLetter || !this.lastNameFirstLetter) {
console.log('Names not ready!');
return;
} else if (this.firstNameFirstLetter && this.lastNameFirstLetter) {
console.log(firstNameFirstLetter + lastNameFirstLetter);
return firstNameFirstLetter + lastNameFirstLetter;
}
}
}
... I get "Names not ready!" printed to the console each time.
By the way, I am running these functions within Angular's ngOnInit life cycle hook, like this:
ngOnInit() {
this.getFirstNameFirstLetter();
this.getLastNameFirstLetter();
this.getInitials(this.firstNameFirstLetter, this.lastNameFirstLetter);
}
I know this has something to do with what's available when, because I get 'undefined' when I use break points and debug the two values being passed into the "getInitials()" function. In other words, the function doesn't have access to the returned values of the other two functions at the time it's run -- hence I'm getting 'Names not ready!' printed to the console. My question is, what am I missing, architecturally, to resolve this kind of issue?
So what is happening here is that JavaScript doesn't think you are using the return values for getFirstNameFirstLetter and getLastNameFirstLetter, so when it makes the call, instead of waiting for that call to finish, it goes on to the next one, which introduces a race condition. if you simply change it to
ngOnInit() {
let temp1 = this.getFirstNameFirstLetter();
let temp2 = this.getLastNameFirstLetter();
this.getInitials(this.firstNameFirstLetter, this.lastNameFirstLetter);
}
then it will wait for the previous functions to finish before calling the next.
Also, I don't use const very often, so I could be wrong and it could follow different scope rules, but by normal scope rules, setting a variable in that function, it is only available in that function, you would need to set it as
this.firstNameFirstLetter = firstName.trim().charAt(0);
to have access to it outside the function.
Or, so as to kill two birds with one stone, you could do
ngOnInit() {
this.firstNameFirstLetter = this.getFirstNameFirstLetter();
this.lastNameFirstLetter = this.getLastNameFirstLetter();
this.getInitials(this.firstNameFirstLetter, this.lastNameFirstLetter);
}
or
ngOnInit() {
let firstNameFirstLetter = this.getFirstNameFirstLetter();
let lastNameFirstLetter = this.getLastNameFirstLetter();
this.getInitials(firstNameFirstLetter, lastNameFirstLetter);
}
depending on if you need the variables again or just for that function.

How to reuse array functions in an infinite loop

The real code is larger, so I won't post it. It looks pretty much like this:
class A {
process(source) {
// I perform several operations with array helper functions here:
const filtered = source.filter(item => item);
const condition = filtered.some(item => item);
if (condition) {
const mapped = source.map(item => /* Mapping operations... */);
const sorted = mapped.sort((a, b) => { /* Some sort conditions... */ });
return sorted;
} else {
const mapped2 = filtered.map(item => /* A different mapping operation... */);
return mapped2;
}
}
}
const a = new A();
while (true) {
const source = getSourceFromSomewhere(); // Array (40 - 50 items aprox)
const b = a.process(source);
// ...
}
The problem: Basically, performance; "Don't make functions within a loop".
On every iteration a bunch of anonymous functions are getting created.
My solution:
class A {
// Predefine it:
sort() { /* Sort logic */ }
map() { /* Map logic */ }
map2() { /* Map logic */ }
filter() { /* Filter logic */ }
some() { /* Condition */ }
process(source) {
const filtered = source.filter(this.filter); // Note: Scope of 'this' is changed.
const condition = filtered.some(this.some);
if (condition) {
const mapped = source.map(this.map);
const sorted = mapped.sort(this.sort);
return sorted;
} else {
const mapped2 = filtered.map(this.map2);
return mapped2;
}
}
}
Another problem: Some of this functions need access to properties of the object itself, but the scope of this has been changed.
It's worth to call .bind(this) instead of creating the anonymous function? or pretty much the same?
What would you do in my case?
Thanks in advance.
To initialize bound functions within a class you could do
class Test {
fn = (t) => this[t]
}
basically the same what you wanted to do anyways.
The problem: Basically, performance; "Don't make functions within a loop".
Your premise is incorrect.
JavaScript engines are highly optimized. They do not laboriously read the source text character-by-character each time through a loop, or each time a function is called, much less each time a callback is invoked. They scan, parse, and pre-compile. At worst, functions like item => item will be created only once per function invocation. More likely, they will be pre-created during the initial scanning and parsing process.
Therefore, you don't need to worry about performance when considering whether to pre-define the functions yourself. The guiding principle should instead be program readability and structure.
If you do want to pre-define a function, as long as it does not use this, consider defining it outside the class:
function filterFunc(item) { return item.val < MAX; }
class A {
process() {
const filtered = source.filter(filterFunc);
If you do need 'this`, then in modern JS it is preferable to write
class A {
filterFunc(item) { return item.val < this.MAX; }
process() {
const filtered = source.filter(item => this.filterFunc(item));
instead of worrying about binding this.filterFunc making you write
class A {
constructor () { this.filterFunc = this.filterFunc.bind(this); }
process() {
const filtered = source.filter(this.filterFunc);
While as mentioned in another answer
class Test {
// constructor etc.
step = x => x + this.currentStep;
process() {
return this.arr.map(step);
}
}
would be a concise way to achieve your intended behavior, as this is already bound to the instance, it requires public class fields which is still in Stage 2, and therefore not yet supported in many browsers without a transpiler.
It is good to remember that you can always pass the this scope to the second argument of functions such as map and filter, so you don't have to manually bind your functions beforehand. The code then becomes
class Test {
// constructor etc.
step(x) { return x + this.currentStep; }
process() {
return this.arr.map(step, this);
}
}
This is very close to the solution you have in mind while making sure your functions have the correct scope.
Though I don't know much about inner workings of browsers I think if the code is hot enough (that is being ran often), the optimized compiler might not need to recreate those anonymous functions every run.

How to have multiple observers to observable queue, but not read duplicate items from queue

Say I have an observable queue class, like so:
function Queue(){
}
Queue.prototype.add = function(item){
return (an Observable);
}
Queue.prototype.read = function(){
return (an Observable);
}
everytime an item is added to the queue, ideally whoever called read would get the next new item.
So for example we have
const q = new Queue({some:data})
q.read().subscribe(); // stream of unique values
q.read().subscribe(); // stream of unique values
...
q.read().subscribe(); // stream of unique values
the Queue instance and read calls might be across separate Node.js process or in the same process, but in any case, I want to ensure that they do not read duplicate values from the queue, I want each read method to return a unique new item in the queue.
Is there a way to implement this with observables? I am pretty much totally new to using them.
Here is a naive and fairly incorrect implementation:
function Queue(){
this.items = [];
this.obsEnqueue = new Rx.Subject();
this._add = function(item){
this.items.push(item);
this.obsEnqueue.onNext(item);
}
}
Queue.prototype.add = function(item){
this._add(item);
// in real life this method will be async so we will return Observable
return (an Observable);
}
Queue.prototype.read = function(){
return this.obsEnqueue
.filter(item => {
return !item.isRead;
})
.map(item => {
item.isRead = true;
return item;
});
}
Surely there is something wrong about the above code, but maybe you can understand what I am trying to do. With multiple callers to the read method, would like to have them each reading unread items.
I am marking them as read in the read method, but I am not sure if that boolean flag is making it's way to the original data source and if other read methods will see that.

walking a tree with Promises

I have a tree structure that I want to walk using Promises, and I haven't figured out the right code pattern for it.
Assume that we're given node names, and the act of converting a node name to a node is an asynchronous process (e.g. involves a web access). Also assume that each node contains a (possibly empty) list of children names:
function getNodeAsync(node_name) {
// returns a Promise that produces a node
}
function childrenNamesOf(node) {
// returns a list of child node names
}
What I want to end up with a method with this signature:
function walkTree(root_name, visit_fn) {
// call visit_fn(root_node), then call walkTree() on each of the
// childrenNamesOf(root_node), returning a Promise that is fulfilled
// after the root_node and all of its children have been visited.
}
that returns a Promise that's fulfilled after the root node and all of its children have been visited, so it might be called as follows:
walkTree("grandpa", function(node) { console.log("visiting " + node.name); })
.then(function(nodes) { console.log("found " + nodes.length + " nodes.")});
update
I've create a gist that shows my first attempt. My (slightly buggy) implementation for walkTree() is:
function walkTree(node_name, visit_fn) {
return getNodeAsync(node_name)
.then(function(node) {
visit_fn(node);
var child_names = childrenNamesOf(node);
var promises = child_names.map(function(child_name) {
walkTree(child_name, visit_fn);
});
return Promise.all(promises);
});
};
This visits the nodes in the correct order, but the outermost Promise resolves before all the sub-nodes have been visited. See the gist for full details.
And as #MinusFour points out, using this technique to flatten the list of nodes is rather pointless. In fact, I really just want the final promise to fire when all the nodes have been visited, so a more realistic use case is:
walkTree("grandpa", function(node) { console.log("visiting " + node.name); })
.then(function() { console.log("finished walking the tree")});
Well it isn't much of a problem to handle a function call for each node, but gathering the node values is kind of a problem. Kind of hard to walk that tree, your best bet would be to map it to a tree with no eventual values. You could use something like:
function buildTree(root_name) {
var prom = getNodeAsync(root_name);
return Promise.all([prom, prom.then(function(n){
return Promise.all(childrenNamesOf(n).map(child => buildTree(child)))
})]);
}
From there on you do:
var flatTree = buildTree(root_name).then(flatArray);
flatTree.then(nodes => nodes.forEach(visit_fn));
flatTree.then(nodes => whateverYouWantToDoWithNodes);
To flatten the array you could use:
function flatArray(nodes){
if(Array.isArray(nodes) && nodes.length){
return nodes.reduce(function(n, a){
return flatArray(n).concat(flatArray(a));
});
} else {
return Array.isArray(nodes) ? nodes : [nodes];
}
}
To be honest, it's pointless to have the tree walker if you want a list of nodes you are better up flattening it up and then iterating the elements, but you can walk the array tree if you want.
Despite what I said in the O.P, I don't really care about the return values of the final promise, but I do want to wait until all the nodes have been traversed.
The problem with the original attempt was simply a missing return statement in the map() function. (Despite appearances, this is essentially structurally identical to #MinusFour's answer.) Corrected form below:
function walkTree(node_name, visit_fn) {
return getNodeAsync(node_name)
.then(function(node) {
visit_fn(node);
var child_names = childrenNamesOf(node);
var promises = child_names.map(function(child_name) {
return walkTree(child_name, visit_fn);
});
return Promise.all(promises);
});
};
Here are two use cases for walkTree(). The first simply prints the nodes in order then announces when the tree walk is finished:
walkTree("grandpa", function(node) { console.log("visiting " + node.name); })
.then(function() { console.log("finished walking the tree")});
The second creates a flat list of nodes, made available when the tree walk completes:
var nodes = [];
walkTree("grandpa", function(node) { nodes.push(node) })
.then(function() { console.log('found', nodes.length, 'nodes);
console.log('nodes = ', nodes); });

Categories

Resources