Javascript - callback with objects - javascript

I have a problem with callback function. I want write a function, who
can iterate the object (i want use a callback method), but it's not
working and i don't know what is wrong with this.
I'll be glad from any help.
services = [
{
name: "a",
},
{
name: "b"
}
]
function Service (data) {
this.name = data.name
}
function getData (i) {
sample = new Service(services[i])
console.log(sample)
}
getData(0) /* this function work*/
function getAll(index, count, callback) {
service = new Service(services[index]);
console.log(service)
if (index < count) {
callback(index + 1, count, getAll)
}
}
getAll (0, services.length, getAll) /* this function is not working */

The error you get is due to calling services[2] that doesn't exists.
This getAll function below solves your problem
function getAll(index, count, callback) {
if (index < count) {
service = new Service(services[index]);
console.log(service)
callback(index + 1, count, getAll)
}
}

The problem is the
getAll (0, services.length, getAll)
services.length return the length of an array, but arrays start at position 0
to fix this error use
getAll (0, services.length-1, getAll)

Related

Recursive Callbacks to create a familly tree

I am trying to create a family tree with callback functions nested in callback functions, i'm hoping to get at least 5 generation. the function receive the person's id, which then look for everyone in the database that has the property 'father' with the same id.
This is the function for getting the children of the person
var getChildren=function (person, callback) {
keystone.list('Person').model.find().where('father', person.id).exec(function(err, children) {
callback(children);
})
}
this is how i use the callback function
function getFamilyTree(person){
getChildren(person, function(children){
person.children=children;
for (var i=0;i<person.children.length;i++) {
!function outer(i){
if (isNotEmpty(person.children[i])){
getChildren(person.children[i],function(children){
person.children[i].children=children;
for (var j=0;j<person.children[i].children.length;j++){
!function outer(j){
if (isNotEmpty(person.children[i].children[j])){
getChildren(person.children[i].children[j],function(children){
person.children[i].children[j].children=children;
for (var k=0;k<person.children[i].children[j].children.length;k++){
!function outer(k){
if (isNotEmpty(person.children[i].children[j].children[k])){
getChildren(person.children[i].children[j].children[k],function(children){
person.children[i].children[j].children[k].children=children;
})
}
}(k);
}
})
}
}(j);
}
});
}
}(i);
}
})
}
as you can see, it is very complicated. It works, but sometimes it doesn't retrieve all 5 generation but only 4 or 3, sometimes even 1 and i don't know why, please help my guys, and i'm also a new comer so please be easy with me, thanks in advance!
If you use promises instead of callbacks, you could use a recursive async function to resolve trees of arbitrary depth:
function getChildren(person) {
return new Promise((resolve, reject) => {
keystone.list('Person').model.find().where('father', person.id).exec((err, children) => {
if(err) reject(err);
else resolve(children);
});
});
}
async function getFamilyTree(person, maxDepth, depth=0) {
if(depth >= maxDepth) return person;
const children = (await getChildren(person)).filter(isNotEmpty);
person.children = await Promise.all(
children.map(child => getFamilyTree(child, maxDepth, depth + 1))
);
return person;
}
getFamilyTree({id: 'rootPersonId'}, 5)
.then(tree => console.log(tree))
.catch(error => console.log(error));
You definitely need to use recursion. I don't know exactly how your data looks, or how getChildren works, but this should point you in the right direction:
function getFamilyTree ( person ) {
getChildren( person, function( children ) {
person.children = children;
for ( var i = 0; i < person.children.length ) {
getFamilyTree( person.children[ i ] );
}
})
}
The first thing you can do to simplify the code is to pull out the outer function that you use multiple times throughout the getFamilyTree function so you don't repeat it all over the place. It'll need an update to take the child node as a parameter instead of the index for simplicity.
Since your callback is the same every time, you can pull it out into it's own function. It'll need a slight update to take in the parent item, since you are constantly referencing the person variable throughout the callback.
Your outer function can be something like:
function outer(child) {
if (isNotEmpty(child)) {
getChildren(child, getChildrenCallback);
}
}
And then the getChildrenCallback will be:
function getChildrenCallback(children, parent) {
parent.children = children;
for ( var i = 0; i < children.length; i++) {
outer(child[i]);
}
}

Creating a simplified underscore _.invoke

I am trying to create underscore's _.invoke function. I can't figure out why I keep getting a TypeError, Cannot read property 'sort' of undefined. I assume this refers to the array being passed into the function, but I can log each array in the collection, so I don't know why undefined in being thrown up.
function each(collection, iteratee, context) {
let i
let boundIteratee = iteratee.bind(context)
if (Array.isArray(collection)) {
for (i = 0; i < collection.length; i++) {
boundIteratee(collection[i], i, context)
}
} else {
for (i in collection) {
if (collection.hasOwnProperty(i)) {
boundIteratee(collection[i], i, collection);
}
}
}
return collection
}
function map(collection, iteratee, context) {
let result = []
let formula = function(element, index) {
result.push(iteratee(element, index, context))
}
each(collection, formula, context)
return result
}
function invoke(collection, methodName) {
let args = Array.prototype.slice.call(arguments, 2)
let formula = function(array, index) {
//console.log(array) --> returns arrays in collection...
return methodName.apply(array, args)
}
return map(collection, formula)
}
function sortIt(array) {
return array.sort()
}
console.log(invoke([
[3, 1, 2],
[7, 6, 9]
], sortIt))
Depending on what you're trying to achieve, you can either replace your sortIt function with:
function sortIt() { return this.sort(); } // since you apply the arrays as context to the function
or replace
return methodName.apply(array, args);
with
return methodName(array);
Neither are ideal, though.
Also, please look up the apply() method.

Controller-independent stepper functions

I have an AngularJS project, and I'm using a modified version of md-steppers, whose interesting functions boil down to this:
var enableNextStep = function () {
//do not exceed into max step
if ($scope.selectedStep >= $scope.maxStep) {
return;
}
//do not increment $scope.stepProgress when submitting from previously completed step
if ($scope.selectedStep === $scope.stepProgress - 1) {
$scope.stepProgress = $scope.stepProgress + 1;
}
};
var completeCurrentStep = function (CurrentStep) {
$scope.stepData[CurrentStep].completed = true;
};
$scope.moveToNextStep = function moveToNextStep() {
if ($scope.selectedStep < $scope.maxStep) {
enableNextStep();
$scope.selectedStep = $scope.selectedStep + 1;
completeCurrentStep($scope.selectedStep - 1); //Complete After changing Step
}
};
$scope.moveToPreviousStep = function moveToPreviousStep() {
if ($scope.selectedStep > 0) {
$scope.selectedStep = $scope.selectedStep - 1;
}
};
The problem is that I would like to use these four functions in two different controllers (so as to not repeat them), that have different stepProgress, selectedStep and maxStep values. I couldn't find a way to do so with services, but I might just be confused about the way AngularJS work, as I am more used to Python.
Thanks.
Abstracting out that functionality into a factory that accepts an array of callbacks and a controller's ng-model would make it more reusable. Of course, ultimately the API you want is up to you. The goal is that you don't want any $scope business inside the factory, it shouldn't be concerned about what's inside the callbacks, it just steps through them.
/**
* #param steps {array} - array of callbacks
*/
function stepperFactory(steps) {
iterate(0, steps);
}
function iterate(current, steps) {
if (!steps[current])
return;
if (typeof steps[current] === 'function')
// pass an async "done" callback
// so your array of input callbacks can be async
// you could also use promises or $q for this
steps[current](() => iterate(current + 1, steps));
}
And so the api you expose would be like:
['stepperFactory', function(stepperFactory) {
this.model = { step: 0, msg: 'start' };
this.steps = [
(done) => {
this.model.step++;
done();
},
(done) => {
setTimeout(() => {
this.model.msg = '3rd step';
this.model.step++;
done();
});
}
];
stepperFactory(this.model, this.steps);
}]
You can use service to share functions which will take maxStep, stepProgress, etc as arguments and instead of modifying the $scope, they will return updated values.
In service:
function moveToPreviousStep(step) {
if (step > 0) {
return (step - 1);
}
return step;
};
and in controller
function moveToPreviousStep() {
$scope.selectedStep = service.moveToPreviousStep($scope.selectedStep);
}
$scope.moveToPreviousStep = moveToPreviousStep;

Setting a variable as an array which can be accessed/updated by every function in an object?

Apologies if this isn't a well constructed post - I'm writing it on my phone because I'm in a meeting but can't get this out of my head!
I've created an object, as follows:
/* CLASS = "HPAnalysisObject" */
/* CONSTRUCTOR */
var HPAnalysisObject = {
points_total: new Array(),
getHPTotals: function(house_id, label) {
for (var i = 1; i <= 26; i++) {
initial = String.fromCharCode(64 + i);
Frog.API.get("users.search", {
"params": {"surname": initial, "group": house_id},
"onSuccess": this.addUsers
});
}
alert(this.getArray());
},
getArray: function() {
return this.points_total;
},
setArray: function(array) {
alert(typeof(array));
},
addUsers: function(data) {
array = new Array(this.getArray);
for (var i = 0; i < data.length; i++) {
if (data[i].profile.id == 200) {
array.push(data[i].id);
}
}
this.setArray(array);
}
};
widget.onLoad = function(){
HPAnalysisObject.getHPTotals(10705, "eagles");
}
The getArray function throws:
TypeError: not a function.
How do you, pseudo, initialise an empty array on init; put new items into that array with one function; then return the array with another?
EDIT: I HAVE OVERHAULED MY PSEUDO-CODE WITH THE ACTUAL CODE
"onSuccess": this.addUsers
The problem is, once the addUsers function is assigned to this property, you're losing the original this scope. One quick solution (that doesn't require a lot of restructuring) is to use a Function.prototype.bind polyfill:
"onSuccess": this.addUsers.bind(this)
But, the more proper fix is to restructure your code to use a closure. With a closure, you can define functions that are "private" and bound to your object instance. Something in the lines of:
var HPAnalysisObjectClass = (function () {
var points_total = []; // "private"
function addUsers(users) { // "private"
// Use points_total, not this.points_total
}
return {
addUsers: addUsers, // Make a "public" copy
getHPTotals: function () {
// ...
Frog.API.get("users.search", {
"params": {"surname": initial, "group": house_id},
"onSuccess": addUsers // not this.addUsers!
});
// ...
}
};
})();

Javascript Array of Functions

var array_of_functions = [
first_function('a string'),
second_function('a string'),
third_function('a string'),
forth_function('a string')
]
array_of_functions[0];
That does not work as intended because each function in the array is executed when the array is created.
What is the proper way of executing any function in the array by doing:
array_of_functions[0]; // or, array_of_functions[1] etc.
Thanks!
var array_of_functions = [
first_function,
second_function,
third_function,
forth_function
]
and then when you want to execute a given function in the array:
array_of_functions[0]('a string');
I think this is what the original poster meant to accomplish:
var array_of_functions = [
function() { first_function('a string') },
function() { second_function('a string') },
function() { third_function('a string') },
function() { fourth_function('a string') }
]
for (i = 0; i < array_of_functions.length; i++) {
array_of_functions[i]();
}
Hopefully this will help others (like me 20 minutes ago :-) looking for any hint about how to call JS functions in an array.
Without more detail of what you are trying to accomplish, we are kinda guessing. But you might be able to get away with using object notation to do something like this...
var myFuncs = {
firstFunc: function(string) {
// do something
},
secondFunc: function(string) {
// do something
},
thirdFunc: function(string) {
// do something
}
}
and to call one of them...
myFuncs.firstFunc('a string')
I would complement this thread by posting an easier way to execute various functions within an Array using the shift() Javascript method originally described here
var a = function(){ console.log("this is function: a") }
var b = function(){ console.log("this is function: b") }
var c = function(){ console.log("this is function: c") }
var foo = [a,b,c];
while (foo.length){
foo.shift().call();
}
Or just:
var myFuncs = {
firstFun: function(string) {
// do something
},
secondFunc: function(string) {
// do something
},
thirdFunc: function(string) {
// do something
}
}
It's basically the same as Darin Dimitrov's but it shows how you could use it do dynamically create and store functions and arguments.
I hope it's useful for you :)
var argsContainer = ['hello', 'you', 'there'];
var functionsContainer = [];
for (var i = 0; i < argsContainer.length; i++) {
var currentArg = argsContainer[i];
functionsContainer.push(function(currentArg){
console.log(currentArg);
});
};
for (var i = 0; i < functionsContainer.length; i++) {
functionsContainer[i](argsContainer[i]);
}
up above we saw some with iteration. Let's do the same thing using forEach:
var funcs = [function () {
console.log(1)
},
function () {
console.log(2)
}
];
funcs.forEach(function (func) {
func(); // outputs 1, then 2
});
//for (i = 0; i < funcs.length; i++) funcs[i]();
Ah man there are so many weird answers...
const execute = (fn) => fn()
const arrayOfFunctions = [fn1, fn2, fn3]
const results = arrayOfFunctions.map(execute)
or if you want to sequentially feed each functions result to the next:
compose(fn3, fn2, fn1)
compose is not supported by default, but there are libraries like ramda, lodash, or even redux which provide this tool
This is correct
var array_of_functions = {
"all": function(flag) {
console.log(1+flag);
},
"cic": function(flag) {
console.log(13+flag);
}
};
array_of_functions.all(27);
array_of_functions.cic(7);
If you're doing something like trying to dynamically pass callbacks you could pass a single object as an argument. This gives you much greater control over which functions you want to you execute with any parameter.
function func_one(arg) {
console.log(arg)
};
function func_two(arg) {
console.log(arg+' make this different')
};
var obj = {
callbacks: [func_one, func_two],
params: ["something", "something else"];
};
function doSomething(obj) {
var n = obj.counter
for (n; n < (obj.callbacks.length - obj.len); n++) {
obj.callbacks[n](obj.params[n]);
}
};
obj.counter = 0;
obj.len = 0;
doSomething(obj);
//something
//something else make this different
obj.counter = 1;
obj.len = 0;
doSomething(obj);
//something else make this different
Execution of many functions through an ES6 callback 🤗
const f = (funs) => {
funs().forEach((fun) => fun)
}
f(() => [
console.log(1),
console.log(2),
console.log(3)
])
Using ES6 syntax, if you need a "pipeline" like process where you pass the same object through a series of functions (in my case, a HTML abstract syntax tree), you can use for...of to call each pipe function in a given array:
const setMainElement = require("./set-main-element.js")
const cacheImages = require("./cache-images.js")
const removeElements = require("./remove-elements.js")
let htmlAst = {}
const pipeline = [
setMainElement,
cacheImages,
removeElements,
(htmlAst) => {
// Using a dynamic closure.
},
]
for (const pipe of pipeline) {
pipe(htmlAst)
}
A short way to run 'em all:
[first_function, ..., nth_function].forEach (function(f) {
f('a string');
});
the probleme of these array of function are not in the "array form" but in the way these functions are called... then...
try this.. with a simple eval()...
array_of_function = ["fx1()","fx2()","fx3()",.."fxN()"]
var zzz=[];
for (var i=0; i<array_of_function.length; i++)
{ var zzz += eval( array_of_function[i] ); }
it work's here, where nothing upper was doing the job at home...
hopes it will help
Using Function.prototype.bind()
var array_of_functions = [
first_function.bind(null,'a string'),
second_function.bind(null,'a string'),
third_function.bind(null,'a string'),
forth_function.bind(null,'a string')
]
I have many problems trying to solve this one... tried the obvious, but did not work. It just append an empty function somehow.
array_of_functions.push(function() { first_function('a string') });
I solved it by using an array of strings, and later with eval:
array_of_functions.push("first_function('a string')");
for (var Func of array_of_functions) {
eval(Func);
}
maybe something like this would do the trick:
[f1,f2,f3].map((f) => f('a string'))
Maybe it can helps to someone.
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<script type="text/javascript">
window.manager = {
curHandler: 0,
handlers : []
};
manager.run = function (n) {
this.handlers[this.curHandler](n);
};
manager.changeHandler = function (n) {
if (n >= this.handlers.length || n < 0) {
throw new Error('n must be from 0 to ' + (this.handlers.length - 1), n);
}
this.curHandler = n;
};
var a = function (n) {
console.log("Handler a. Argument value is " + n);
};
var b = function (n) {
console.log("Handler b. Argument value is " + n);
};
var c = function foo(n) {
for (var i=0; i<n; i++) {
console.log(i);
}
};
manager.handlers.push(a);
manager.handlers.push(b);
manager.handlers.push(c);
</script>
</head>
<body>
<input type="button" onclick="window.manager.run(2)" value="Run handler with parameter 2">
<input type="button" onclick="window.manager.run(4)" value="Run handler with parameter 4">
<p>
<div>
<select name="featured" size="1" id="item1">
<option value="0">First handler</option>
<option value="1">Second handler</option>
<option value="2">Third handler</option>
</select>
<input type="button" onclick="manager.changeHandler(document.getElementById('item1').value);" value="Change handler">
</div>
</p>
</body>
</html>
This answered helped me but I got stuck trying to call each function in my array a few times. So for rookies, here is how to make an array of functions and call one or all of them, a couple different ways.
First we make the array.
let functionsArray = [functionOne, functionTwo, functionThree];
We can call a specific function in the array by using its index in the array (remember 0 is the first function in the array).
functionsArray[0]();
We have to put the parenthesis after because otherwise we are just referencing the function, not calling it.
If you wanted to call all the functions we could use a couple different ways.
For loop
for (let index = 0; index < functionsArray.length; index++) {
functionsArray[index]();
}
Don't forget the parenthesis to actually call the function.
ForEach
ForEach is nice because we don't have to worry about the index, we just get handed each element in the array which we can use. We use it like this (non arrow function example below):
functionsArray.forEach(element => {
element();
});
In a ForEach you can rename element in the above to be whatever you want. Renaming it, and not using arrow functions could look like this:
functionsArray.forEach(
function(funFunctionPassedIn) {
funFunctionPassedIn();
}
);
What about Map?
We shouldn't use Map in this case, since map builds a new array, and using map when we aren't using the returned array is an anti-pattern (bad practice).
We shouldn't be using map if we are not using the array it returns, and/or
we are not returning a value from the callback. Source
I know I am late to the party but here is my opinion
let new_array = [
(data)=>{console.log(data)},
(data)=>{console.log(data+1)},
(data)=>{console.log(data+2)}
]
new_array[0]
you got some top answers above. This is just another version of that.
var dictFun = {
FunOne: function(string) {
console.log("first function");
},
FuncTwo: function(string) {
console.log("second function");
},
FuncThree: function(string) {
console.log("third function");
}
}
/* PlanetGreeter */
class PlanetGreeter {
hello : { () : void; } [] = [];
planet_1 : string = "World";
planet_2 : string = "Mars";
planet_3 : string = "Venus";
planet_4 : string = "Uranus";
planet_5 : string = "Pluto";
constructor() {
this.hello.push( () => { this.greet(this.planet_1); } );
this.hello.push( () => { this.greet(this.planet_2); } );
this.hello.push( () => { this.greet(this.planet_3); } );
this.hello.push( () => { this.greet(this.planet_4); } );
this.hello.push( () => { this.greet(this.planet_5); } );
}
greet(a: string) : void { alert("Hello " + a); }
greetRandomPlanet() : void {
this.hello [ Math.floor( 5 * Math.random() ) ] ();
}
}
new PlanetGreeter().greetRandomPlanet();

Categories

Resources