JS Promise when looping through array of chrome.bookmarks.getChildren [duplicate] - javascript

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 4 years ago.
I'm developing a google chrome extension and I have to loop trough the nodes (folders) to check how many items I have within each folder. I'm suppling an item ID to the function getBookmarksCount(ID). I'm having an issue to get a result from the main function the console.log() returns correct value at the point of logging.
Here is my code:
const getBookmarksCount = (bmkNode) => {
let nodes = []
let result = 0
new Promise ((resolve, reject) => {
chrome.bookmarks.getChildren(bmkNode, (bmkChildren) => {
_.each(bmkChildren, (item) => {
// Check if the item is a bookmark link
if (!(item.url === undefined || item.url === null)) {
nodes.push(item.title)
}
})
resolve(_.size(nodes))
})
}).then((size) => {
console.log(size) //The correct number of items is listed here eg. 6
result = size
})
return result
}
//I'm suppling a parent folder ID the function should return number of children
getBookmarksCount(123) // eg. 6 -> at the moment returns 0
Here is my updated working version without Promise. setTimeout() is a dirty hack but works. Any suggestions how I can improve this function?
const getBookmarksCount = (bmkNode) => {
let nodes = []
const getChildrenCount = (bmkNode) => {
chrome.bookmarks.getChildren(bmkNode, (bmkChildren) => {
_.each(bmkChildren, (item) => {
// if is bookmark otherwise loop trough subfolder
(!(item.url === undefined || item.url === null)) ? nodes.push(item.title): getChildrenCount(item.id)
})
})
setTimeout(() => {
$(`#counter_${bmkNode}`).html(_.size(nodes))
}, 50)
}
getChildrenCount(bmkNode)
}
// HTML Template
<label class="label label-primary" id="counter_${item.id}">0</label>

As pointed out by Bravo in the comments, you are not really waiting for your code to execute. You are ever so close, though!
const getBookmarksCount = (bmkNode) => {
return new Promise ((resolve) => {
let nodes = []
chrome.bookmarks.getChildren(bmkNode, (bmkChildren) => {
_.each(bmkChildren, (item) => {
// Check if the item is a bookmark link
if (!(item.url === undefined || item.url === null)) {
nodes.push(item.title)
}
})
resolve(_.size(nodes))
})
})
}
getBookmarksCount(123).then(size => {
console.log(size)
})
Note the return new Promise on the second line, which is a key difference from your provided snippet. By doing this, you wait to "return" here until you actually finish the async work and call resolve. Then, to get the value returned, you would do the same .then syntax you used but at the call of getBookmarksCount.
Hopefully that helps and, of course, also works!

Related

How to return the value of an array that was filled with an ASYNC function in Javascript/Node? [duplicate]

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 2 years ago.
I have googled and googled and can't seem to get out of this wet paper bag I find myself in.
I have an exported function that grabs a bunch of data from a web api, then we do some stuff to that data, go back and grab a little more data, and then finally return a javascript object back. At least that's the plan. I have everything working right up to the point where I have to send the data back from the module into my main app.js file.
Here is what I hope to be the relevant code from my function...there is lots I omitted, but I don't think it's relevant.
//SNIP
let airportBoards = [...results];
const updateBoards = () => {
airportBoards.forEach(airport => {
console.log(airport)
boardTypes.forEach(board => {
let currentOffset = airport[board].next_offset;
(async function () {
while (currentOffset > 0) {
await getOffsetData(airport.airport, board, currentOffset) //axios request
.then(res => {
currentOffset = res.data.AirportBoardsResult[board].next_offset
newFlights = res.data.AirportBoardsResult[board].flights
console.log(currentOffset)
let index = airportBoards.findIndex(r => r.airport === airport.airport)
airportBoards[index][board].flights.push(...newFlights)
})
}
})()
})
})
}
Ultimately what I need to do is get the contents of the "airportBoards" back out of this. Everything is working as far as I can tell, it loops through the offsets, and calls until there is no more data to return. And I can see the airportBoards array is updating properly using the debugging tools...I just can't figure out how to use it (airportBoards) once all the async/await stuff is settled.
Apologies in advance...I know its not pretty...I just hope I have provided enough information here for someone to give me a hand.
Thanks in advance!
You can use Promise.all to wait for all promises to resolve:
let airportBoards = [...results];
const updateBoards = () => {
let arrayOfPromises = [];
airportBoards.forEach(airport => {
console.log(airport)
boardTypes.forEach(board => {
let currentOffset = airport[board].next_offset;
let promise = (async function() {
while (currentOffset > 0) {
await getOffsetData(airport.airport, board, currentOffset)
.then(res => {
currentOffset = res.data.AirportBoardsResult[board].next_offset
newFlights = res.data.AirportBoardsResult[board].flights
console.log(currentOffset)
let index = airportBoards.findIndex(r => r.airport === airport.airport)
airportBoards[index][board].flights.push(...newFlights)
})
}
})();
arrayOfPromises.push(promise);
});
})
Promise.all(arrayOfPromises).then(() => { /* finish - do whatever you want */ });
}

Console.log not waiting for the promise [duplicate]

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 2 years ago.
I am very new to JavaScript, trying to understand the concepts of asynchronous function. So, basically I wrote a webScraper for RateMyProfessor using nightmare. This is the function:
var Nightmare = require("nightmare"),
nightmare = Nightmare();
const baseURL =
"https://www.ratemyprofessors.com/search.jsp?queryBy=schoolId&schoolName=University+of+California+Santa+Barbara&schoolID=1077&queryoption=TEACHER";
const getRatingProfessor = (professorName) => {
let rating;
let nameSplitter = professorName.split(" ");
if (nameSplitter.length > 2) {
professorName = nameSplitter[0] + " " + nameSplitter[1];
}
nightmare
.goto(baseURL)
.click("#ccpa-footer > .close-this")
.type("#professor-name", professorName)
.wait(1000)
.evaluate(() => {
var resultList = document.querySelectorAll("a .rating");
//if no result is found
if (typeof resultList === "undefined" || resultList.length == 0) {
return "Not Found";
}
//Found the professor with exact name
if (resultList.length == 1) {
return document.querySelector("a > .rating").innerHTML;
}
//conficting similar professor names (rare case)
if (resultList.length >= 2) {
return "Cannot Determine";
}
})
.end()
.catch((err) => {
console.log(err);
})
.then((text) => {
rating = text;
console.log(professorName, text);
});
return rating;
};
console.log(getRatingProfessor("XXXXX"));
If I run this program, it gives the following output:
undefined
SomeProfName 4.8
It seems like the function returned the rating to console.log without waiting for the promise. Why isn't function not waiting for the nightmare promise to get resolved. More importantly, why isn't the value of rating getting updated; or it has been updated but console.log doesn't want to wait for the function?
Sorry, these questions may look absurd, but I would really appreciated the answers :0
Your function explicitly does not return anything hence will return undefined by default. What you are getting is correct but within the then()...
If you wanted to return an async result you'd need to declare your function as async and then await the result.
const getRatingProfessor = async (professorName) => {
let rating;
let nameSplitter = professorName.split(" ");
if (nameSplitter.length > 2) {
professorName = nameSplitter[0] + " " + nameSplitter[1];
}
await text = nightmare...
return text;
};
(async () => {
console.log(await getRatingProfessor("XXXXX"));
}
)()

Node.js can't access var in subfunction [duplicate]

This question already has answers here:
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
(7 answers)
Closed 2 years ago.
With the following function I am trying to calculate the total price of a checkout. But if I console.log() the variable before it is returned, I get 0. If I console.log() the variable in the findOne() function, I get the correct value.
As database I use MongoDB and "Item" is a model.
function calculateOrderAmount(items) {
var totalPayment=0;
items.forEach((item) => {
Item.findOne( { _id: item } )
.then(item => {
if(item) {
// Item exists
totalPayment = item.price;
console.log(totalPayment);
}
});
});
console.log(totalPayment);
return totalPayment;
}
I'm desperate about it and I don't really know what to look for on the internet. Many thanks for answers in advance.
Item.findOne is an async operation, so in your code you execute:
var totalPayment = 0
items.forEach((item) => {...
console.log(totalPayment)
return totalPayment
other sync code who called calculateOrderAmount
then the callback of Item.findOne is run
You must use a callback sytle or an async function like this:
async function calculateOrderAmount (items) {
// beware: if you have a huge items list, doing so could lead to high memory usage
const itemsPromises = items.map((item) => {
return Item.findOne({ _id: item })
.then(item => {
if(item) {
// Item exists
return item.price;
}
return 0
});
})
const prices = await Promise.all(itemsPromises)
const totalPayment = prices.reduce((a, b) => a + b, 0)
console.log(totalPayment)
return totalPayment
}

Adapting a function that isnt chainable to return a value

I am trying to get all the pages of a pdf in one object using the pdfreader package. The function originally returns each page (as its own object) when it processes it. My goal is to write a wrapper that returns all pages as an array of page objects. Can someone explain why this didn't work?
I tried:
adding .then and a return condition - because I expected the parseFileItems method to return a value:
let pages = [];
new pdfreader.PdfReader()
.parseFileItems(pp, function(err, item) {
{
if (!item) {
return pages;
} else if (item.page) {
pages.push(lines);
rows = {};
} else if (item && item.text) {
// accumulate text items into rows object, per line
(rows[item.y] = rows[item.y] || []).push(item.text);
}
}
})
.then(() => {
console.log("done" + pages.length);
});
and got the error
TypeError: Cannot read property 'then' of undefined
The function I'm modifying (From the package documentation):
var pdfreader = require("pdfreader");
var rows = {}; // indexed by y-position
function printRows() {
Object.keys(rows) // => array of y-positions (type: float)
.sort((y1, y2) => parseFloat(y1) - parseFloat(y2)) // sort float positions
.forEach(y => console.log((rows[y] || []).join("")));
}
new pdfreader.PdfReader().parseFileItems("CV_ErhanYasar.pdf", function(
err,
item
) {
if (!item || item.page) {
// end of file, or page
printRows();
console.log("PAGE:", item.page);
rows = {}; // clear rows for next page
} else if (item.text) {
// accumulate text items into rows object, per line
(rows[item.y] = rows[item.y] || []).push(item.text);
}
});
There seem to be several issues/misconceptions at once here. Let's try to look at them once at a time.
Firstly, you seem to have thought that the outer function will return ("pass on") your callback's return value
This is not the case as you can see in the library source.
Also, it wouldn't even make sense, because the callback called once for each item. So, with 10 items, it will be invoked 10 times, and then how would parseFileItems know which of the 10 return values of your callback to pass to the outside?
It doesn't matter what you return from the callback function, as the parseFileItems function simply ignores it. Furthermore, the parseFileItems function itself doesn't return anything either. So, the result of new pdfreader.parseFileItems(...) will always evaluate to undefined (and undefined obviously has no property then).
Secondly, you seem to have thought that .then is some sort of universal chaining method for function calls.
In fact, .then is a way to chain promises, or to react on the fulfillment of a promise. In this case, there are no promises anywhere, and in particular parseFileItems doesn't returns a promise (it returns undefined as described above), so you cannot call .then on its result.
According to the docs, you are supposed to react on errors and the end of the stream yourself. So, your code would work like this:
let pages = [];
new pdfreader.PdfReader()
.parseFileItems(pp, function(err, item) {
{
if (!item) {
// ****** Here we are done! ******
console.log("done" + pages.length) // The code that was in the `then` goes here instead
} else if (item.page) {
pages.push(lines);
rows = {};
} else if (item && item.text) {
// accumulate text items into rows object, per line
(rows[item.y] = rows[item.y] || []).push(item.text);
}
}
})
However, I agree that it'd be nicer to have a promise wrapper so that you won't have to stuff all the following code inside the callback's if (!item) branch. You could achieve that like this, using new Promise:
const promisifiedParseFileItems = (pp, itemHandler) => new Promise((resolve, reject) => {
new pdfreader.PdfReader().parseFileItems(pp, (err, item) => {
if (err) {
reject(err)
} else if (!item) {
resolve()
} else {
itemHandler(item)
}
})
})
let pages = []
promisifiedParseFileItems(pp, item => {
if (item.page) {
pages.push(lines)
rows = {}
} else if (item && item.text) {
// accumulate text items into rows object, per line
(rows[item.y] = rows[item.y] || []).push(item.text)
}
}).then(() => {
console.log("done", pages.length)
}, e => {
console.error("error", e)
})
Note: You would get even nicer code with async generators but that is too much to explain here now, because the conversion from a callback to an async generator is less trivial than you may think.
If you want to chain a then, you need the callback function to return a Promise :
new pdfreader.PdfReader()
.parseFileItems(pp, function (err, item) {
return new Promise( (resolve, reject) => {
let pages = ...
// do stuff
resolve(pages);
}
})
.then( pages => {
console.log("done" + pages.length);
});

forEach function scoping - how to parse array to function without repeat function calls

I have a function that I call and within the function I use a promise, I also want to call this function ( it's actually the second function called in the code) first, retrieve some data, then parse this data through after my second function which calls out to also retrieve some data.
getAllProductGroups(): Promise<void> {
return new Promise((resolve, reject) => {
this.allEngagementTypes().subscribe(data => {
const products = {};
const potentialEvents = [];
data.forEach(product => {
// products[product.id] = product.description;
this.potentialForEvents(this.organization.id, product.id)
.subscribe(potentialEventData => {
potentialEvents.push(potentialEventData);
this.allEngagementAreas().subscribe(areas => {
this.organization.addAreas(areas, potentialEvents);
resolve();
}, error => reject(error));
});
});
})
});
}
I call the forEach on the first function call allEngagementTypes, as I need to use each element to then make my second function call on potentialForEvents, then I create an array with the responses with potentialEvents.push,
I want to then with this array, parse it after my third call 'allEngagementAreas' as when this calls on the 'addAreas' function, I want to parse the array potentialEvents, but because of the forEach It is sent 21 times, only two of which area actually sent before the 'addAreas' fully loads, I need just one array to be sent and for it to be parsed and be ready before the allEngagementAreas is actually called.
Any help is greatly appreciated.
if I understood your problem correctly:
getAllProductGroups(): Promise<void> {
return new Promise((resolve, reject) => {
this.allEngagementTypes().subscribe(data => {
const products = {};
const potentialEvents = [];
data.forEach(product => {
// products[product.id] = product.description;
this.potentialForEvents(this.organization.id, product.id)
.subscribe(potentialEventData => {
potentialEvents.push(potentialEventData);
if(potentialEvents.length === someConstValue) { //not sure how, but you need to find out the needed array length
resolve(potentialEvents);
}
});
});
})
}).then(potentialEvents => {
this.allEngagementAreas().subscribe(areas => {
this.organization.addAreas(areas, potentialEvents);
}, error => reject(error));
})
}
main idea is to split it to two promises, and resolve the first one when the array if filled. in forEach loops they usually compare the result length with some const value.

Categories