I want to create a tree using a recursive function. The input to this function is a node and I want to add its children to it with that recursive function.
The following code will explain my problem in a better way:
function getUpstreamChildrenRecusrively(node) {
var receiverId = localStorage.getItem("ReceiverId");
//API call to get the children node
axios({
method: 'get',
url: window.location.origin+"/api/rwa/coverageView/getUpstreamChildren?id="+node.elementId,
headers: {
"ReceiverId":receiverId
}
})
.then(response => {
localStorage.setItem("ReceiverId",response.headers["receiverid"]);
var data = response.data;
for(var i = 0; i < data.length; i++) {
var obj = data[i];
var result = {};
result.text = obj.print;
result.elementId = obj.id;
result.elementText = obj.text;
result.expanded = true;
result.visible = true;
result.icon = window.location.origin+"/api"+obj.image;
getUpstreamChildrenRecusrively(result);
node.nodes = []; //nodes property will contain children
node.nodes.push(result);
console.log("Tree so far:"+JSON.stringify(node));
}
})
.catch(error => {
})
}
For every recursive call, the value of the node is a separate node having a single child in nodes property. I want to see the node to be grown with all its children as a final result.
What am I missing in this code?
Thank you in advance!
It looks like you expect your getUpstreamChildrenRecusrively to work synchronously. Learn more about javascript async/await and Promises.
here is how it should probably work
async function getUpstreamChildrenRecusrively(node) {
const receiverId = localStorage.getItem("ReceiverId")
const response = await axios({
method: 'get',
url: window.location.origin+"/api/rwa/coverageView/getUpstreamChildren?id="+node.elementId,
headers: {
ReceiverId: receiverId
}
})
localStorage.setItem("ReceiverId",response.headers["receiverid"])
const data = response.data
node.nodes = node.nodes || []
for(let i = 0; i < data.length; i++) {
const obj = data[i]
const result = {}
result.text = obj.print
result.elementId = obj.id
result.elementText = obj.text
result.expanded = true
result.visible = true
result.icon = window.location.origin + "/api" + obj.image
node.nodes.push(result)
await getUpstreamChildrenRecusrively(result)
}
}
getUpstreamChildrenRecusrively(initialNode).then(() => {
console.log('result node', initialNode)
})
Your understanding of how recursion works is flawed, I am not trying to be rude, just trying to help you understand that you need to study the subject more.
First of all you are not returning anything from your function
You are also checking the value of node after you have called your function recursively (which performs an async call and is scoped to the current function call).
You are making recursive api calls with no check in place for when the function should stop executing. Which means it will run until your api call fails.
function getUpstreamChildrenRecusrively(node) {
var receiverId = localStorage.getItem("ReceiverId");
//Api call to get the children node
return axios({
method: "get",
url:
window.location.origin +
"/api/rwa/coverageView/getUpstreamChildren?id=" +
node.elementId,
headers: {
ReceiverId: receiverId
}
})
.then(response => {
localStorage.setItem("ReceiverId", response.headers["receiverid"]);
var data = response.data;
for (var i = 0; i < data.length; i++) {
var obj = data[i];
var result = {};
result.text = obj.print;
result.elementId = obj.id;
result.elementText = obj.text;
result.expanded = true;
result.visible = true;
result.icon = window.location.origin + "/api" + obj.image;
node.nodes = getUpstreamChildrenRecusrively(result); //nodes property will contain children
console.log("Tree so far:" + JSON.stringify(node));
return node;
}
})
.catch(error => {
/* I am using ES6 here, you can use something equavelant to check if node has a value */
if (Object.keys(node).length > 0) {
return node;
} else {
/* obviously you need other error handling logic here too */
}
});
}
Related
I am trying to make axios POST requests inside a for loop. However, the axios post POST request is being run n times with only the last value of the loop. Running twice with value sheetName="Virudhunagar Beds". Below is my piece of code:
const axios = require('axios')
const sheetDistrictMap = {
"Ariyalur Beds": "5ea0abd3d43ec2250a483a4f", "Virudhunagar Beds": "5ea0abd3d43ec2250a483a58"
}
let promises = [];
for (sheetName in sheetDistrictMap) {
promises.push(
axios.post('https://tncovidbeds.tnega.org/api/hospitals', {
District: sheetDistrictMap[sheetName],
FacilityTypes: ["CHO", "CHC", "CCC"],
IsGovernmentHospital: true,
IsPrivateHospital: true,
pageLimit: 100
})
.then(res => {
var outputJsonArray = [];
for (i = 0; i < res.data.result.length; i++) {
var rowJson = {};
var rowdata = res.data.result[i];
rowJson["Name"] = rowdata["Name"];
outputJsonArray.push(rowJson);
}
console.log("Parsed sheet: " + sheetName);
console.log(outputJsonArray);
console.log("========================================================");
})
.catch(error => {
console.error(error)
})
)
}
Promise.all(promises).then(() => console.log("All data scraped"));
How do I make async calls with each loop parameter?
You are hit the closure inside loops issue. You can use let and const keywords to declare the sheetName variable. Each iteration through the loop will have a new variable sheetName with loop scope.
for (let sheetName in sheetDistrictMap) {
// the rest code
}
For more info, see JavaScript closure inside loops – simple practical example
Try adding const to the sheetName variable. The problem is that your variable without it behaves like var that is global in most cases. Adding a const makes the variable block-scoped so that every iteration will have a unique variable instead of using the last value that was assigned to the sheetName.
for (const sheetName of Object.keys(sheetDistrictMap)) {
promises.push(
axios.post('https://tncovidbeds.tnega.org/api/hospitals', {
District: sheetDistrictMap[sheetName],
FacilityTypes: ["CHO", "CHC", "CCC"],
IsGovernmentHospital: true,
IsPrivateHospital: true,
pageLimit: 100
})
.then(res => {
var outputJsonArray = [];
for (i = 0; i < res.data.result.length; i++) {
var rowJson = {};
var rowdata = res.data.result[i];
rowJson["Name"] = rowdata["Name"];
outputJsonArray.push(rowJson);
}
console.log("Parsed sheet: " + sheetName);
console.log(outputJsonArray);
console.log("========================================================");
})
.catch(error => {
console.error(error)
})
)
}
I think there are two things here :
let or const in the for loop which creates a global scope for the variable
How you are getting data from the promises. You don't need to .then after pushing it to the array instead do this:
const axios = require('axios')
const sheetDistrictMap = {
"Ariyalur Beds": "5ea0abd3d43ec2250a483a4f", "Virudhunagar Beds": "5ea0abd3d43ec2250a483a58"
}
let promises = [];
for (let sheetName in sheetDistrictMap) {
promises.push(
axios.post('https://tncovidbeds.tnega.org/api/hospitals', {
District: sheetDistrictMap[sheetName],
FacilityTypes: ["CHO", "CHC", "CCC"],
IsGovernmentHospital: true,
IsPrivateHospital: true,
pageLimit: 100
})
)
}
Promise.all(promises).then((data) =>
//data will array and in same sequence you hit the api
// do the below processing for every response
var outputJsonArray = [];
for (i = 0; i < res.data.result.length; i++) {
var rowJson = {};
var rowdata = res.data.result[i];
rowJson["Name"] = rowdata["Name"];
outputJsonArray.push(rowJson);
}
//console.log("Parsed sheet: " + sheetName); // make sure you get the sheetName
console.log(outputJsonArray);
console.log("========================================================");
})
.catch(error => {
console.error(error)
})
I'm attempting to make an API call using promises. The API is paginated and as such, depending on the headers in that first API call make more to get the rest of the results if need be.
Here's what I have so far:
const get = (url, pageNo) => {
var options = {
url: url,
headers: {
'Authorization': `Token token=${apiToken}`
},
json: true,
page: pageNo
};
return new Promise((resolve, reject) => {
request.get(options, (err, resp) => {
err ? reject(err) : resolve(resp);
})
});
};
Using get() to loop and get all responses:
const getAll = (plannerId, timestamp, range) => {
const plannerBookingsUrl = new URL(
`/api/planners/${plannerId}/bookings?since=${timestamp}&range=${range}`,
baseUrl
);
let response = get(plannerBookingsUrl, 1);
let bookings = [];
bookings.push(response);
response.then(results => {
let moreRequests = true;
let currentPage = 1;
const totalPages = parseInt(results.headers['x-total-pages']);
while (moreRequests) {
if (currentPage < totalPages) {
nextBatch = get(plannerBookingsUrl, currentPage + 1);
bookings.push(nextBatch);
currentPage++;
} else {
moreRequests = false;
}
}
});
return Promise.all(bookings);
};
Main() where I'm using getAll(...):
const main = () => {
const response = getAll(
'11716',
'2020-02-27',
'7'
);
response.then(results => {
console.log(results);
.catch(error => console.log(error))
};
main();
This returns the initial promise but not the remaining promises.
What I'm really have a problem with is reading the first API, making the remainder and returning them all together to be using in my main function.
Any help would be much appreciated!
Thanks.
You could put all your fetching logic inside the while loop. The way you get your bookings is the same, except for the first time where you need to get a little more information on the amount of pages.
Accomplish this by making your function async and check the first time of the loop if the totalPages value is already known. If it's not, await the response and get the info from the headers, and otherwise just push the response to the bookings array.
const getAll = async (plannerId, timestamp, range) => {
const plannerBookingsUrl = new URL(
`/api/planners/${plannerId}/bookings?since=${timestamp}&range=${range}`,
baseUrl
);
let bookings = [];
let currentPage = 1;
let totalPages = null;
while (totalPages === null || currentPage < totalPages) {
let response = get(plannerBookingsUrl, currentPage);
if (totalPages === null) {
let results = await response;
totalPages = parseInt(results.headers['x-total-pages']);
}
bookings.push(response);
currentPage++;
}
return Promise.all(bookings);
};
The problem is that you are returning Promise.all(bookings) outside response.then callback, so at this point bookings contains only the first call get(plannerBookingsUrl, 1).
Here is a possible solution using async:
const getAll = async (plannerId, timestamp, range) => {
const plannerBookingsUrl = new URL(
`/api/planners/${plannerId}/bookings?since=${timestamp}&range=${range}`,
baseUrl
);
let response = get(plannerBookingsUrl, 1);
let bookings = [];
bookings.push(response);
const results = await response; // wait for results here
let moreRequests = true;
let currentPage = 1;
const totalPages = parseInt(results.headers['x-total-pages']);
while (moreRequests) {
if (currentPage < totalPages) {
nextBatch = get(plannerBookingsUrl, currentPage + 1);
bookings.push(nextBatch);
currentPage++;
} else {
moreRequests = false;
}
}
return Promise.all(bookings); // bookings now contains every next batch
};
adapt on main() function:
const main = async () => {
const results = await getAll(
'11716',
'2020-02-27',
'7'
);
...
};
main();
Hi i would like to know if it is possible use "break" or any other method to skip the crawling function after running the script for the first time and just use the array variable that contains the crawling information for the next user request.
let glossary = [];
/** Initialise crawling and save it into glossary array */
function init() {
const glossary_url = 'https://xxxx';
const headers = {
cookie: 'cookies:kdkjslkd424'
};
const options = {
url: glossary_url,
method: 'GET',
headers: headers
};
request(options, function (error, response, body) {
const newDom = new jsdom(body);
const $ = require('jquery')(newDom.window);
$('ul > li > span[style="line-height:1.6;"]').each(function (index, element) {
let text = element.textContent.trim(); // Get text from html
let splitText = text.split(' = '); // split text by =
//console.log(text);
if (splitText.length > 1) {
glossary.push({
key: splitText[0].trim(),
value: splitText[1],
value2: splitText[2]
});
}
});
//console.log(glossary);
findMatch('DPDL');
});
}
break init;
function findMatch (key){
for(i = 0; i < glossary.length ; i++) {
if (glossary[i].key === key){
// console.log (glossary[i].value );
// console.log(glossary[i].key);
// console.log(glossary[i].value2);
// console.log(key);
console.log(key + ' = ' + glossary[i].value + ' ' + glossary[i].value2 );
}
}
}
init();
break or skip the crawl function if user wants to search another value it will just find in the glossary array glossary = [] and not crawl again as it takes long time
Here is the code:
buildImgSuccess(json) {
if (typeof json !== "undefined") {
if (json.filesUploaded.length) {
//-- Update Saving Status --//
this.setState({
saving: true
});
//-- Set Vars --//
let item = 0;
let sortOrder = 0;
let imgCount = this.state.images.length;
if (!imgCount) {
imgCount = 0;
}
while (item < json.filesUploaded.length) {
//-- Determine if PDF Document was Uploaded --//
if (json.filesUploaded[item].mimetype === "application/pdf") {
//-- Handle Document Upload --//
//-- Get Number of pages --//
let theKey = json.filesUploaded[item].key;
let theHandle = json.filesUploaded[item].handle;
axios.get(`/getPhotos`, {
headers: {
"Content-Type": 'application/json'
},
transformRequest: (data, headers) => { delete headers.common.Authorization; }
}).then(jsonResult => {
let pageCount = 1;
Our lint compiler is producing this error
Don't make functions within a loop
Anytime there is a .then() or .catch() inside a loop.
Does anyone understand what the problem is with this code structure and any possible solutions?
Thanks!
You need to create the function outside of the while loop.
Creating inside recreates the function every loop which is non performant.
See below.
Simplified Example - Wrong
const i = 0;
while (i < 10) {
const print = (i) => console.log(i++); //Created 10 times (0,1,...,8,9)
print(i);
};
Simplified Example - Correct
const i = 0;
const print = (i) => console.log(i++); //Created once
while (i < 10) {
print(i);
};
With your code
const handleJsonResult = (jsonResult) => {
let pageCount = 1;
//...
}
while (item < json.filesUploaded.length) {
//-- Determine if PDF Document was Uploaded --//
if (json.filesUploaded[item].mimetype === "application/pdf") {
//-- Handle Document Upload --//
//-- Get Number of pages --//
let theKey = json.filesUploaded[item].key;
let theHandle = json.filesUploaded[item].handle;
axios.get(`/getPhotos`, {
headers: {
"Content-Type": 'application/json'
},
transformRequest: (data, headers) => { delete headers.common.Authorization; }
}).then(handleJsonResult);
//...
}
I can't get the finalList filled to use in my html file, it wil run the code to fill it before the promise all code. I need to use this array in my html document so it has to be a this.variable I am using Aurelia.
activate() {
var repoList = [];
var repos = this.http.fetch({Link to github api})
.then(response => response.json())
.then(repos => this.repos = repos);
var trello = new Trello;
trello.getBoards().then(boardList => this.boards = boardList);
var boards = trello.getBoards();
//add github repo to the associated trello board (works)
Promise.all([boards, repos]).then(function(values) {
var count = 0;
for (var i in values[0]) {
for (var a in values[1]) {
if (values[1][a].hasOwnProperty("name")) {
var repo = values[1][a].name.toLowerCase();
var board = values[0][i]['name'].toLowerCase();
repoList[count] = repo;
count++;
if (repo == board) {
console.log(repo + " " + board)
}
}
}
}
});
//this list is always empty (The problem)
this.finalList = repoList;
this.title = "Trello Boards";
}
Something like this should do it. Hard to decipher what's going on in the for loops.
activate() {
let reposPromise = this.http.fetch({Link to github api})
.then(response => response.json());
let boardsPromise = new Trello().getBoards();
return Promise.all([boardsPromise, reposPromise])
.then(([boards, repos]) => {
this.boards = boards;
this.repos = repos;
this.finalList = [];
for (var i in boards) {
for (var a in repos) {
if (values[1][a].hasOwnProperty("name")) {
var repo = values[1][a].name.toLowerCase();
var board = values[0][i]['name'].toLowerCase();
this.finalList.push(repo);
if (repo == board)
{
console.log(repo + " " + board)
}
}
}
}
});
this.title = "Trello Boards";
}
I believe Your finalList should be set inside the promise handler. Like this.
activate() {
var repoList = [];
//I always use this, and I am not sure what do you mean
//by this.finalList, but still I assume you know what you are doing
//And hence I use this!
var that = this;
var repos = this.http.fetch({Link to github api})
.then(response => response.json())
.then(repos => this.repos = repos);
var trello = new Trello;
trello.getBoards().then(boardList => this.boards = boardList);
var boards = trello.getBoards();
//add github repo to the associated trello board (works)
Promise.all([boards, repos]).then(function(values) {
var count = 0;
for (var i in values[0]) {
for (var a in values[1]) {
if (values[1][a].hasOwnProperty("name"))
{
var repo = values[1][a].name.toLowerCase();
var board = values[0][i]['name'].toLowerCase();
repoList[count] = repo;
count++;
if (repo == board)
{
console.log(repo + " " + board)
};
}
};
};
//I believe when promise resolves. You should set the repoList.
that.finalList = repoList;
that.title = "Trello Boards";
});
}
My question is, do you really wanna set title and finalList to this? Just asking.
Hope this helps!