combo of async map, recursion and callback in function- nodejs - javascript

I have a function which computes a value and after the computation returns value with a callback to a another function.
The situation is starting to get pretty mixed where I have 2 nested for loops and recursion inside this.
Error is: uncaughtException: Callback was already called.
First let me write the sample of the code.
functionTest (array, function (err, result) {
if (err) {
nresponse.error(err);
} else {
nresponse.success(res, result);
}
);
function dependicies(array, callback) {
async.map(array, function(item, outerNext) {
async.map(item.members, function(value, innerNext){
dependicies(array, callback);
innerNext(); //should I write this after or before the recursion call?
outerNext(); //where should I call the outerNext?
});
}, function(err, result){
**
if(err){
callback(err);
}else{
callback(null, sthComputedInMap);
}
});
}
As I write in comments, should I write innerNext after or before the recursion call? In addition to the this question, should I call outerNext after the scope of second map?
Things are pretty messed. How am I gonna clear up? I'm looking the document.
** isn't this a place where it's the end of the first async.map. I think the problem is this is called for each async.map of the recursion. What I want is to call this as break.
In code I try to loop through an array. Assume I try to get list of name fields of one document's starting from itself to its last grand grand children. The array's structure which I trace is like this;
[
{id: 1, childrenIds: [3, 4, 5], name: ""},
{id: 3, childrenIds: [8, 5], name: ""},
{id: 21, childrenIds: [ 5], name: ""},
{id: 7, childrenIds: [5], name: ""},
{id: 5, childrenIds: [], name: ""}
]
first async.map is for traversing each document, the other one is for traversing its children array.
I hope it helps :)

You can mix loops, recursion and callbacks using SynJS. Below is a working example to illustrate. setTimeout is just used as an example of asynchronous function that returns result via callback.
global.SynJS = global.SynJS || require('synjs');
function processOneDoc(modules, documents, doc, childNames) {
for(var i=0; doc.childrenIds && i < doc.childrenIds.length; i++) {
var currDoc = documents[doc.childrenIds[i]];
if(currDoc) {
childNames.push(currDoc.name);
var chNames=[];
setTimeout(function(){
SynJS.run(modules.processOneDoc, null, modules, documents, currDoc, chNames, function(){
SynJS.resume(_synjsContext);
});
if(chNames.length)
childNames.push(chNames);
},2000);
SynJS.wait();
}
}
};
function processAll(modules, documents) {
for(var d in documents) {
var childNames=[];
SynJS.run(modules.processOneDoc,null, modules, documents, documents[d], childNames,function(){
SynJS.resume(_synjsContext);
});
SynJS.wait();
console.log(new Date().toISOString(), d, childNames);
}
}
var modules = {
SynJS: SynJS,
processOneDoc: processOneDoc,
};
var documents = {
1: {id: 1, childrenIds: [3, 4, 5], name: "name of 1st"},
3: {id: 3, childrenIds: [8, 5], name: "name of 3rd"},
21: {id: 21, childrenIds: [ 5], name: "name of 21st"},
7: {id: 7, childrenIds: [5], name: "name of 7th"},
5: {id: 5, childrenIds: [], name: "name of 5th"}
};
SynJS.run(processAll,null,modules,documents,function () {
console.log('done');
});
It would produce following output:
2017-01-06T18:50:57.750Z 1 [ 'name of 3rd', [ 'name of 5th' ], 'name of 5th' ]
2017-01-06T18:50:59.785Z 3 [ 'name of 5th' ]
2017-01-06T18:50:59.800Z 5 []
2017-01-06T18:51:01.831Z 7 [ 'name of 5th' ]
2017-01-06T18:51:03.863Z 21 [ 'name of 5th' ]
done

Based on my understanding for what you have provided in your question. Your recursion call is not on the right location. One way to solve your problem is as:
function dependencies(array, callback){
outerResult = async.map(array, function(item, outerNext){
var innerResult = async.map(item.members, function(value, innerNext){
//recursive_call_condition == true
// for instance you want to check if value has an array further
if(value.members.length)
dependencies(value.members, callback);
else
innerNext(null, innerResult);
});
outerNext(null, outerResult);
}, function(err, finalResult){
// `finalResult` is your result
callback(finalResult)
});
}

Related

run console one time inside map function using javascript / node js [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 1 year ago.
Improve this question
I have an array and I want to store its variable in files as I have declare files outside the map function
let object = [
{
id: '01',
name: 'Subject',
'Data.type': 'maths',
},
{
id: '02',
name: null,
'Data.type': 'science',
},
{
id: '04',
name: 'language',
'Data.type': 'node',
},
{
id: '05',
name: null,
'Data.type': 'node',
},{
id: '01',
name: 'Subject',
'Data.type': 'maths',
},
{
id: '02',
name: 'Subject',
'Data.type': 'science',
},
{
id: '04',
name: null,
'Data.type': 'node',
},
{
id: '05',
name: null,
'Data.type': 'node',
}
];
let names=[];
object.map((value) => {
if(typeof value.name === "string"){
names.push(value.name);
console.log(names);
}
//some code here
//code here
// use of names to perform some task
})
the value is printed like this
["subject"]
["subject",
"language"]
["subject",
"language",
"subject"]
["subject",
"language",
"subject",
"subject"]
is there any possibility it should only run 1 time inside map function with fully loaded value inside like this so i can perform task inside the map function
files= ["subject",
"language",
"subject",
"subject"]
Array.prototype.map():
The purpose of array.map is to create a new array where each element is changed to the result of the original element gone through the provided function.
For example,
var a = [1, 2, 3];
function times2(n) {
return n * 2;
}
var b = a.map(times2);
console.log(b); // Will log [2, 4, 5]
Array.prototype.forEach():
There is also the function array.forEach. The purpose of this is to run a function with each element of the array as an input in order.
For example,
a = [1, 2, 3];
a.forEach(console.log); // Will log: "1", then "2", then "3".
Your solution:
You are wanting to run a function on each element of your array.
Your function should take one element as an input, and push it to another existing array.
You should wait until every element has been added to the new array to log it.
For example,
let names = [];
function addToNames(element) {
if (typeof element.name === "string") {
names.push(element.name);
}
}
object.forEach(addToNames);
console.log(names);
// Now that names array is complete, you can do whatever you need with it.
Another option, using arrow functions,
let names = [];
object.forEach(element => {
if (typeof element.name === "string") {
names.push(element.name);
}
});
console.log(names);
// Now that names array is complete, you can do whatever you need with it.

JS | lodash : recursive delete from deep nested array

Below is a recursive method to delete a comment from a deeply nested array. The code works, but here are my question:
QUESTION:
I'm using _.remove within the loop to find and remove a comment in the current array. It seems expensive for obvious reasons i.e. loop within a loop, but other approaches seems just as expensive. I'm sure there are better ways to do this.
WORKING EXAMPLE:
https://plnkr.co/edit/PeW5ZFLynO2q8VNqbAHx?p=preview
var comments = [
{
id: 1,
depth: 0,
subject: 'Subject one'
},
{
id: 2,
depth: 0,
subject: 'Subject two',
children: [
{
id: 3,
depth: 1,
subject: 'Subject two dot one'
},
{
id: 4,
depth: 1,
subject: 'Subject two dot two'
}
]
},
{
id: 5,
depth: 0,
subject: 'Subject three',
children: [
{
id: 6,
depth: 1,
subject: 'Subject three dot one'
},
{
id: 7,
depth: 1,
subject: 'Subject three dot two',
children: [
{
id: 8,
depth: 2,
subject: 'Subject three dot two dot one'
},
{
id: 9,
depth: 2,
subject: 'Subject three dot two dot two'
}
]
}
]
}
];
function deleteComment(comment, comments) {
var self = this,
db = [];
function removeComment(items, parent) {
_.forEach(items, function (item) {
// QUESTION - seems expensive as we have a loop in a loop
_.remove(items, function(item) {
if (item.id === comment.id) {
console.log(item);
return true;
}
// NOTE: use above for demo purposes
// return item.id === comment.id
});
_.has(item, 'children') ? removeComment(item.children, item) : 0;
});
}
removeComment(comments, db);
}
var commentToBeDeleted = {
id: 8,
depth: 2,
subject: 'Subject three dot two dot one'
};
deleteComment(commentToBeDeleted, comments);
You could probably find a way to do this more efficiently with a .reduce() function to combine .forEach and _.remove. However, if the code works, it works!
I am not sure if this is the most performant way to accomplish this, but this is the most succinct way I have found:
It turns out JSON.stringify provides a callback for each visited JSON value being converted, which you can use to determine if the value should be included in the string. You can use this to visit each value without having to do the traversing yourself.
From MDN
The replacer parameter can be either a function or an array. As a
function, it takes two parameters, the key and the value being
stringified. The object in which the key was found is provided as the
replacer's this parameter. Initially it gets called with an empty key
representing the object being stringified, and it then gets called for
each property on the object or array being stringified. It should
return the value that should be added to the JSON string,
In your case the function would look something like
function deleteComment(commentToBeDeleted, comments) {
return JSON.parse(JSON.stringify(comments, function(key, value) {
if (commentToBeDeleted.id !== value.id) {
return value;
}
}));
}
Note: you probably don't want to use this code as is, as it leaves an empty node, but, you can insert what ever you logic you like into the callback, and this should get you started.

Rethinkdb append to array if exists

In RethinkDB, I have a table authors with the following layout:
{
id: 12,
videos: [1,2,3]
}
Now I get new authors with objects like this:
{
id: 12,
videos: [4,5]
}
If the author now already exists, I want to append the new videos 4 and 5 to the list of videos.
If author not exists, just insert the document like it is.
My approach was the following, but it didn't work.
r.table('authors').getAll(4, 3, 2, 1, {index: 'id'})
.replace(function(author, index) {
return r.branch(
author.eq(null),
{
id: index,
videos: [1,2,3]
},
author
);
})
-> Response:
{
"deleted": 0 ,
"errors": 3 ,
"first_error": "Expected 2 arguments but found 1." ,
"inserted": 0 ,
"replaced": 0 ,
"skipped": 0 ,
"unchanged": 0
}
Thanks!
Your logic is very good. It is just some syntax issue.
Given an author, if the author isn't existed, insert it, otherwise, append the video array, here is what I think of, using your logic:
var author = {
id: 12,
videos: [9, 10]
};
r.table('authors').insert(author).do(
function (doc) {
return r.branch(doc('inserted').ne(0),
r.expr({inserted: 1}),
r.table('authors').get(author["id"]).update(function(doc) {
return {videos: doc('videos').union(author["videos"])}
})
)
}
)
If the insert is sucesfully, it means we have no document with the same id, we don't have to do anything. Otherwise, we will update the document and append the videos into it.
To updare an array of multiple author, we can use foreach and expr to turn array into ReQL object. However, in this case, we use bracket to get field instead of using [] as in JavaScript object
var authors = [{
id: 12,
videos: [90, 91]
},{
id: 14,
videos: [1, 2]
}];
r.expr(authors).forEach(function(author) {
return r.table('authors').insert(author).do(
function (doc) {
return r.branch(doc('inserted').ne(0),
r.expr({inserted: 1}),
r.table('authors').get(author("id")).update(function(doc) {
return {videos: doc('videos').union(author("videos"))}
})
)
}
)
})
Something like this should do it:
r([4, 3, 2, 1]).foreach(function(id) {
return r.table('authors').get(id).replace(function(row) {
return r.branch(row.eq(null), {id: id, videos: [1, 2, 3]}, row);
});
});
lambda for replace method has only 1 argument. But you are trying too pass 2 args: author, index.

Wrap the function into another function

I have the code that sorts an array of objects by the object property using lodash. It’s pretty simple:
_.sortBy(data.cycles, "id");
However, I discovered that data.cycles[].id might be a string, so I have to convert each property to number before passing it to _.sortBy. Is there any elegant way of doing it? I think I should combine _.property and parseInt somehow, but I don’t know how.
data.cycles might look like this:
[
{
"id": "20",
"name": "John"
},
{
"id": "15",
"name": "Sarah"
},
{
"id": "158",
"name": "Bill"
},
{
"id": "-6",
"name": "Jack"
},
{
"id": "59",
"name": "Bob"
}
]
I would compose a sortBy() callback as follows:
var collection = [
{ id: '20', name: 'John' },
{ id: 15, name: 'Sarah' },
{ id: 158, name: 'Bill' },
{ id: '-6', name: 'Jack' },
{ id: 59, name: 'Bob' }
];
_.sortBy(collection, _.flow(_.property('id'), parseInt));
// →
// [
// { id: '-6', name: 'Jack' },
// { id: 15, name: 'Sarah' },
// { id: '20', name: 'John' },
// { id: 59, name: 'Bob' },
// { id: 158, name: 'Bill' }
// ]
The flow() function is useful when you want to compose a callback function out of many functions, it just forwards the output to the next function in line. The property() function returns a function that gets the specified property name from it's argument. This is the id, and that gets passed to parseInt().
We're essentially mapping and sorting at the same time. This has an efficiency advantage, in addition to being a one-liner: it doesn't have to iterate over the whole collection twice. This isn't a big deal with smaller collections, but with larger collections, the impact is noticeable.
You might want to run map first and modify id, then do a sort.
In vanilla JS, it would look something like:
function mapFn(item){
item.id = parseInt(item.id, 10);
return item;
}
var sortedCycles = data.cycles.map(mapFn).sort(sortFn);
In lodash, you could have:
var sortedCycles = _.sortBy(_.map(data.cycles, mapFn), 'id');
// or
var sortedCycles = _.chain(data.cycles).map(mapFn).sortBy('id').value()

Loop to display hierarchical data

I am creating an array out of essentially hierachical data, for example as below:
[
{id: 1, title: 'hello', parent: 0, children: [
{id: 3, title: 'hello', parent: 1, children: [
{id: 4, title: 'hello', parent: 3, children: [
{id: 5, title: 'hello', parent: 4},
{id: 6, title: 'hello', parent: 4}
]},
{id: 7, title: 'hello', parent: 3}
]}
]},
{id: 2, title: 'hello', parent: 0, children: [
{id: 8, title: 'hello', parent: 2}
]}
]
I am looking to loop through the array, but can't get my head around how to recursively loop down to create an unordered list where each child level is indented.
Trying to do this in JavaScript, but need a push in the right direction for the construction of the loop to drill down until there are no more children, and then back up to the top array.
Any help would be appreciated.
I answered a question about this before
Here is demo for it: http://jsfiddle.net/zn2C7/7/
var list = $("<ul>");
function populatedata() {
$.each(data.FolderList, function (i, folder) {
if (folder.ParentFolderID == -1) {
var item = $("<li>").html(folder.FolderName);
list.append(item);
var children = $('<ul>');
item.append(children);
checkChild(folder.FolderID, children);
}
});
$('body').append(list);
}
function checkChild(parentid, parent) {
$.each(data.FolderList, function (i, folder) {
if (folder.ParentFolderID == parentid) {
var item = $("<li>").html(folder.FolderName);
var children = $('<ul>');
parent.append(item);
item.append(children);
checkChild(folder.FolderID, children);
}
else {
return ;
}
});
}
It was possible to build it using html variable, like you tried to do that, but it is much simpler to use DOM manipulation functions of jQuery ($('<ul>') and $('<li>') - create new element, .append() - append element to some other element)
function checkChild(parentid) {
$.each(data.FolderList, function (i, folder) {
if (folder.ParentFolderID == parentid) {
html += '<li><ul>' + folder.FolderName;
checkChild(folder.FolderID);
html+=</ul></li>
return html;
}
else {
return ;
}
});
}
Also, please note that in code above you are doing return html; from each function callback. Not sure what you wanted to get exactly, but in .each it may work like break in regular loop (if you will return false):
We can stop the loop from within the callback function by returning false.
That is from jquery api page.
Also, for such tasks I prefer to use debugger. At this moment there are a lot of powerful tools for HTML/CSS/JS debugging in browser. Just press F12 in Chrome, IE or FF (for the last one you may need to install Firebug extension) and you will get a lot of helpful features along with simple JS debugging.

Categories

Resources