Beginner here...
So I wanted to make what I thought was a simple change, but it has proven to be more difficult. I have a .js script that runs on a timer as a function. The function queries data and brings back a list of objects that turn into an array of ids of records in the first loop.
In its original format -
// get pledge objects
const pledges = await getOppPledges()
//Check if there are records
if (pledges && pledges.length > 0) {
//get array of pledge opportunity ids
for (let pledge of pledges) {
let oppIds = pledges.map(p => p.Opportunity__c)
let sfOpps = await getOpp(oppIds)
//run loop to create oppty doc
for await (let opp of sfOpps) {...
}
...}
My problem is that the double "for" loop generates double documents when there is more than one pledge. When I tried closing the first loop right before the second loop, "sfOpps" was unavailable for the second loop, and the code broke.
I had rearranged the code to something like,
if (pledges && pledges.length > 0) {
for (let pledge of pledges) {
let oppIds = [pledge.Opportunity__c]
let opp = await getOpp(oppIds)
But it seems like it returns "opp" as an array instead of an object. This does not work either because it comes back as undefined when I call for a value of "opp" like "opp.OpportunityContactRoles.totalSize".
Any input is appreciated!
If I understood your problem correctly you shouldn't be needing the first loop as you map your ids on all your pledges inside the loop. It's the reason why you do the operations n times. (n being the length of the "pledges" array)
let oppIds = pledges.map(p => p.Opportunity__c) // You're already mapping all the ids at once
The correct way to do it would be
// get pledge objects
const pledges = await getOppPledges()
//Check if there are records
if (pledges && pledges.length > 0) {
//get array of pledge opportunity ids
let oppIds = pledges.map(p => p.Opportunity__c)
let sfOpps = await getOpp(oppIds)
//run loop to create oppty doc
for await (let opp of sfOpps) {...
}
Related
In the code bellow I work with CSS DOM, which may be computation heavy. This is apparently necessary to access the :after selector. renderItem method will add a new item to the DOM (including it's after element) and this is the reason why I have used the async function and await for it's return in each iteration inside of loadFromStorage.
However, the await seems to not work correctly, or something weird happens inside of renderItem function. The n iterator is updated correctly at the beginning of the function (items are correctly rendered to the screen and the first console.debug prints a correct value in a correct order), but at the bottom, the second printed value, is always the last iteration value (which is 4 in my case, as I am trying to render 4 items from the local storage) and getCSSRule method is getting a wrong number.
let books = []
let n = 0
const renderItem = async (entry, direction = 1) => {
const li = document.createElement('li')
const ul = document.querySelector('ul')
li.classList.add('item')
n += 1
console.debug(`iter: ${n}`)
li.id = (`item${n}`)
await addCSSRule(`#item${n}:after`)
li.innerText = entry.slice(0, entry.length - 13)
if (direction === 1)
ul.appendChild(li)
else
ul.insertBefore(li, ul.firstChild)
console.debug(`iter: ${n}`)
const s = await getCSSRule(`#item${n}::after`).catch(() => {
console.debug(`Failed to find ':after' selector of 'item${n}'`)
return false
})
s.style.content = "\""+ entry.slice(entry.length - 13, entry.length) +"\""
return true
}
const loadFromStorage = () => {
books = localStorage.getItem('books').split('//')
books.forEach(async (entry) => {
await renderItem(entry)
})
}
...
Console result (considering localStorage.getItem('books').split('//') returns 4 items):
iter: 1
iter: 2
iter: 3
iter: 4
iter: 4 // Printed x4
I been also trying to pass this renderItem method to await inside of a Promise object, which give me the same result. Also when I update the n iterator at the end of function the same thing happens, but at the beginning of it.
I am sorry if some terminology I have used is not correct in the context of JavaScript, I been not using this language for many years and currently I am trying to catch on.
The key problem here is that you're passing an async function to forEach, so even though you're awaiting inside the body of it, forEach will not wait for the function itself. To illustrate the order of events here, say you have 4 books A, B, C, D. Your execution will look something like this.
renderItem(A)
n += 1 (n is now 1)
console.log(n) (logs 1)
await addCSSRule(`#item${1}:after`) (This is a truly asynchronous event, and so this frees up the event loop to work on other things, namely the next elements in the forEach)
renderItem(B)
n += 1 (2)
console.log(n) (logs 2)
...
renderItem(C) ... n += 1 (3) ... await addCSSRule
renderItem(D) ... n += 1 (4) ... await addCSSRule
And then whenever the addCSSRule calls resolve n will always be 4 no matter which call you're in.
Solution
Use a for await...of loop instead of Array.prototype.forEach.
for await (const entry of books) {
await renderItem(entry);
}
Or a traditional for loop, and modify renderItem to take n as an argument
for (let i = 0; i < books.length; i++) {
renderItem(books[i], i+1);
// we don't need to await in this case, and we add 1 to i so that the 'n' value is 1-indexed to match your current behaviour.
}
I would prefer the latter option as it's best practice to avoid mutable global state (your n variable) - as it can lead to confusing control flow and issues just like the one you're having.
One other option is to set a local variable to the value of n after incrementing it inside renderItem, so that for the duration of that function the value won't change, but that seems like a very hacky workaround to me.
new to coding and first time poster here. i apoligze if this has been answered before, but i'm not exactly sure what i would look up to find it anyhow..
i'm going through a for loop rendering elements of an array that's pulled from an api. my goal is to only print 10 items from that array based on element of that array (i.e. not the first 10 items of the array but the first 10 items of the array that meet a criteria)
something like this:
for(let i=0;i<json.length;i++)
{
let product = json[i]
let category = product.category
renderProduct() //this is a function that prints the product to the DOM
}
in my api, each object has a category, let's say some are X, some are Y, some are Z, some are Q, etc....I want to be able to print the first 10 that are X - hoping this makes sense, and thank you all for your help and input!
You could iterate with a check for the category and continuethe loop if not wanted and use a counter to count wanted product until zero and exit the loop.
let count = 10;
for (const product of json) {
if (product.category !== 'X') continue;
renderProduct(product);
if (!--count) break;
}
You need to use check inside your array and execute renderProduct() function outside the loop:
var json = [{id:1,category:'X'},{id:2,category:'X'},{id:3,category:'Y'}...]; // this array contain list of your items
var topTen = [];
for(let i=0;i<json.length;i++)
{
let product = json[i]
let category = product.category
if(category == 'X' && topTen.length <= 9){
topTen.push(product);
}
if(topTen.length >= 10){
break;
}
}
console.log('first 10 items by category: ', topTen);
renderProduct() //this is a function that prints the product to the DOM
Check this example HERE
Good luck
I have a JavaScript loop
for (var index = 0; index < this.excelData.results.length; index++) {
let pmidList = this.excelData.results[index]["PMIDList"];
if (pmidList.length == 0 ){
continue;
}
let count = 0;
let pmidsList = pmidList.split(',');
if (pmidsList.length > 200){
pmidList = pmidsList.slice(count, count+ 200).join(',');
} else {
pmidList= pmidsList.join(",");
}
// Create some type of mini loop
// pmidList is a comma separated string so I need to first put it into an array
// then slice the array into 200 item segments
let getJSONLink = 'https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi?'
getJSONLink += 'db=pubmed&retmode=json&id=' + pmidList
await axios.get(getJSONLink)
.then(res => {
let jsonObj = res.data.result;
//Do Stuff with the data
}
}).catch(function(error) {
console.log(error);
});
//Loop
}
the entire process works fine EXCEPT when the PMIDList has more than 200 comma separated items. The web service only will accept 200 at a time. So I need to add an internal loop that parses out the first 200 hundred and loop back around to do the rest before going to the next index and it would be nice to do this synchronously since the webservice only allows 3 requests a second. And since I'm using Vue wait is another issue.
The easiest option would be to use async await within a while loop, mutating the original array. Something like:
async function doHttpStuff(params) {
let getJSONLink = 'https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi?'
getJSONLink += 'db=pubmed&retmode=json&id=' + params.join('')
return axios.get(getJSONLink)
}
and use it inside your inner while loop:
...
let pmidsList = pmidList.split(',');
// I'll clone this, because mutating might have some side effects?
const clone = [...pmidsList];
while(clone.length) {
const tmp = clone.splice(0, 199); // we are mutating the array, so length is reduced
const data = await doHttpStuff(tmp);
// do things with data
}
...
So today I was learning about some ES6 array helpers, and I wanted to change my existing for loops with them, but I can not get same result as i was taking with for loop
function comment(){
let index;
for(let i = 0; i < commentArray.length; i++){
if(commentArray[i]._id == req.params.commentId){
index = commentArray.indexOf(commentArray[i]);
}
}
return index;
}
var com = comment();
It is nodejs and I am trying to get element index from database, and than pull from it, my code works just fine, but I want to change it with array helper, I guess I need find helper, but I can not make it work!
You can replace your for loop with this one-liner which uses Array#findIndex:
let index = commentArray.findIndex(comment => comment._id === req.params.commentId);
When this line executes, it will assign the index of comment, which has _id attribute same as req.params.commentId.
If you want to find the index of the item in the array based on some condition, you can use findIndex function
commentArray.findIndex(comment => comment._id === req.params.commentId)
Also with your current code with for loop, I think you need return the index as soon as it is found and not let the loop iterate till the end.
for(let i = 0; i < commentArray.length; i++){
if(commentArray[i]._id == req.params.commentId){
return commentArray.indexOf(commentArray[i]);
}
}
I've been struggling with this piece for a few days and I can't seem to find what's wrong. I have an array with a few objects:
myMainPostObj.categories = [Object, Object]
This is for add/removing categories to a post. Imagine I'm editing an existing post which is already associated with a couple of categories (as per code above).
I also have an array which has all categories in the db (including the ones which are associated with the post). On my js controller I have the following code:
$scope.addCategory = function (cat) {
for (var i in $scope.post.category_ids) {
if (cat._id === $scope.post.category_ids[i]) {
$scope.post.category_ids.slice(i, 1);
} else if (cat._id !== $scope.post.category_ids[i]) {
$scope.post.category_ids.push(cat._id);
}
}
}
The above function is called every time the user click on a category. The idea is for the above function to loop through the categories within the post (associated with the post) and compares it with the category passed as argument. If it matches the function should remove the category. If it doesn't it should add.
In theory this seems straight forward enough, but for whatever reason if I tick on category that is not associated with the post, it adds two (not one as expected) category to the array. The same happens when I try to remove as well.
This is part of a Angular controller and the whole code can be found here
The error in your code is that for each iteration of the loop you either remove or add a category. This isn't right... You should remove if the current id matches but add only if there was no match at all. Something like this:
$scope.addCategory = function (cat) {
var found = false;
for (var i in $scope.post.category_ids) {
if (cat._id === $scope.post.category_ids[i]) {
$scope.post.category_ids.splice(i, 1); // splice, not slice
found = true;
}
}
if (!found) // add only if it wasn't found
$scope.post.category_ids.push(cat._id);
}
I guess the problem could be that you're altering the category_ids array while you're iterating over it with the for loop. You might be better off trying something like this:
$scope.addCategory = function (cat) {
var catIndex = $scope.post.category_ids.indexOf(cat._id);
if (catIndex > -1)
$scope.post.category_ids.splice(catIndex, 1);
else
$scope.post.category_ids.push(cat._id);
}
Note that indexOf doesn't seem to be supported in IE7-8.
Let's simplify this a bit:
const CATEGORIES = [1, 2, 3];
let addCategory = (postCategories, categoryId) => {
CATEGORIES.forEach((cId, index) => {
if (postCategories[index] === cId) console.log("Removing", categoryId);
else console.log("Adding", categoryId);
});
return postCategories;
};
Please ignore the fact that we actually are not mutating the array being passed in.
A is either equal or not equal to B - there is no third option (FILE_NOT_FOUND aside). So you are looping over all of your categories and every time you don't find the category ID in the array at the current index you add it to the postCategories array.
The proper solution to your problem is just to use a Set (or if you need more than bleeding edge ES6 support, an object with no prototype):
// Nicer, ES6-or-pollyfill option
var postCategories = new Set();
postCategories.add(categoryId);
// Or, in the no-prototype option:
var postCategories = Object.create(null);
postCategories[categoryId] = true;
// Then serialization needs an extra step if you need an array:
parentObject.postCategories = Object.keys(parentObject.postCategories);