Array of promises in a promise array - javascript

I have an array which contains books data. I have to loop in array and make an service call to fetch details of each book and each book data has id's of attachments associated to book and make a service calls to fetch associated attachments for each book.
Here issue is promise.all not waiting for aAttachmentPromises to get resolved
function ExportbooksData(books) {
return new Promise((resolve, reject) => {
if (books && books.length > 0) {
let aPromises = [];
for (let i = 0; i < books.length; i++) {
const id = books[i].id;
const name = books[i].name;
aPromises.push(this.getBooksData(name, id, null).then(results => {
let aAttachmentPromises = [];
Object.entries(results).forEach(([key, value]) => {
let fieldName = key;
if (value.constructor === Array && value.length > 0) {
aAttachmentPromises.push(this.getAttachments(fieldName).then(fileContent => {
}))
}
});
}));
}
// Resolve when all are done!
Promise.all(aPromises)
.then(results => resolve(results))
.catch(error => reject(error));
}
})
}

I refactored this live in the BrisJS meetup in Brisbane, Australia tonight. Here is the video where I do it and explain the changes: https://youtu.be/rLzljZmdBNM?t=3075.
Here is a repo with both your version and the refactor, with mocked services: GitHub repo
function getAttachmentsForBookWithMetadataArray(bookdata) {
return Object.entries(bookdata)
.filter(([_, value]) => value.constructor === Array && value.length > 0)
.map(([fieldname, _]) => getAttachments(fieldname));
}
function getAttachmentsForBook(book) {
return getBookData(book).then(getAttachmentsForBookWithMetadataArray);
}
function ExportbooksData(books) {
return !books || !books.length > 0
? Promise.reject(new Error("Did not get an array with 1 or more elements"))
: Promise.all(books.map(getAttachmentsForBook));
}
For a discussion on dealing with failures, see this article: Handling Failure and Success in an Array of Asynchronous Tasks

You build up aAttachmentPromises but you don't return it, so your aPromises.push always pushes a Promise that resolves immediately with undefined into the array that you're waiting on. Change your code like this:
aPromises.push(
this.getBooksData(name, id, null).then(results => {
let aAttachmentPromises = [];
Object.entries(results).forEach(([key, value]) => {
let fieldName = key;
if (value.constructor === Array && value.length > 0) {
aAttachmentPromises.push(this.getAttachments(fieldName)
.then(fileContent => {})
.catch(err => {
if (err == "minor") console.log("Ignoring error",err);
else throw err;
})
);
}
});
return Promise.all(aAttachmentPromises); // <--- here!
})
);
But in addition to that you can simplify the function also. You don't need to wrap everything in a new Promise object, and using a variable to hold a value that's used only once is not helpful. This simplified version is (imho) easier to read/maintain:
function ExportbooksData(books) {
let aPromises = [];
for (let i = 0; books && i < books.length; i++) {
aPromises.push(
this.getBooksData(books[i].name, books[i].id, null).then(results => {
let aAttachmentPromises = [];
Object.entries(results).forEach(([key, value]) => {
if (value.constructor === Array && value.length > 0) {
aAttachmentPromises.push(this.getAttachments(key).then(fileContent => {}));
}
});
return Promise.all(aAttachmentPromises);
})
);
}
return Promise.all(aPromises);
}

Related

Converting a for loop function results into an array

I am looping through an array of selected index comparing each value to a database of machine pricing, and returning the price of each selected index. the problem is, the result repData1 return individual results, I want those resuls to displayed in an array for I can manipulate the array.
I have tried push, concat.... string results is displayed for each item rather than a whole.
for (let a = 0; a < selectedindex.length; a++) {
wixData
.query('MachinePricing')
.contains('title', selectedindex[a])
.find()
.then(async (results) => {
if (results.items.length > 0) {
let repData = results.items;
let repData1 = repData.map(({ prices }) => prices);
console.log(repData1);
}
});
}
Don't loop async calls using iterators
Instead do this
const a = 0
const repData = [];
function getData = () => {
if (a >= selectedindex) {
processRepData();
return;
}
wixData
.query('MachinePricing')
.contains('title', selectedindex[a])
.find()
.then(results => {
if (results.items.length > 0) {
repData.concat(results.items.map(({prices}) => prices));
}
a++;
getData()
});
}
getData()
I think what you are doing is this (run a query for each selected index and extract the returned prices into an array):
const queries = selectedindex.map(ix => wixData
.query('MachinePricing')
.contains('title', ix)
.find())
const results = await Promise.all(queries)
const prices = results.flatMap(r => r.items.map(i => i.prices))

React filter multiple object key

trying to work with Minimals.cc and I have a problem with the search feature that include only the names and that I need to extends to other key of the objects I'm filtering (filterAll). Here is the code:
function applySortFilter({ tableData, comparator, filterName, filterCircle, filterAll }) {
const stabilizedThis = tableData.map((el, index) => [el, index]);
stabilizedThis.sort((a, b) => {
const order = comparator(a[0], b[0]);
if (order !== 0) return order;
return a[1] - b[1];
});
tableData = stabilizedThis.map((el) => el[0]);
// this is the one i'm trying to make that should includes both item.name and item.circle_name
if (filterAll) {
tableData = tableData.filter((item) => item.name.toLowerCase().indexOf(filterAll.toLowerCase()) !== -1);
}
if (filterName) {
tableData = tableData.filter((item) => item.name.toLowerCase().indexOf(filterName.toLowerCase()) !== -1);
}
if (filterCircle) {
tableData = tableData.filter((item) => item.circle_name.toLowerCase().indexOf(filterCircle.toLowerCase()) !== -1);
}
return tableData;
}
I tried playing with && and || in the filter method to add item.circle_name and item.name but it did not work (at least for the way I did it).
Thanks in advance.
After trying more the answer that would work is:
if (filterAll) {
tableData = tableData.filter((item) => (item.name.toLowerCase().indexOf(filterAll.toLowerCase()) && item.circle_name.toLowerCase().indexOf(filterAll.toLowerCase())) !== -1);
}

Javascript map, reduce not working when implemented within object method

Based on the answer from this question I implemented the map reduce code within an object method.
this.displayValueGraph = async () => {
let scaleData = [];
this.positions.forEach(async (pos, i) => {
scaleData[i] = [];
let gdata = await pos.graphData;
gdata.option.forEach((d) => {
scaleData[i].push(d.map((x) => x * pos.size));
});
});
let out;
if (scaleData.length == 1) {
out = scaleData[0];
} else {
out = scaleData.reduce((a, b) => b.map((x, j) => x.map((v, k) => a[j][k] + v)));
}
};
The code by itself works fine. I have taken the input data (above scaleData) and run it through the map reduce function and the output is as expected. But if I include it as part of this method it does nothing. It doesn't throw any errors, it simply returns an empty array.
I have tried adding an empty array as an "initial value", but it doesn't help.
The root cause of the problem appears to have been the first forEach loop, where I included an await. I replaced the forEach with for in and it solved the problem.
this.displayValueGraph = async () => {
let scaleData = [];
for (const i in this.positions) {
const pos = this.positions[i];
scaleData[i] = [];
let gdata = await pos.graphData;
gdata.option.forEach((d) => {
scaleData[i].push(d.map((x) => x * pos.size));
});
}
let out;
if (scaleData.length == 1) {
out = scaleData[0];
} else {
out = scaleData.reduce((a, b) => b.map((x, j) => x.map((v, k) => a[j][k] + v)));
}
};

Promise ReaddirSync not returning values

I have files in the directories, the let resolvers = { ...resolversArray[0], ...resolversArray[1] }; line does get set, however I'm trying to make it so I don't manually need to put in each item of the array.
Here is my code
let resolversArray = [];
let promise = new Promise((resolve, reject) => {
fs.readdirSync(`${__dirname}/modules`).forEach((folder) => {
let temp = require(`./modules/${folder}/resolver.js`);
resolversArray[folder] = temp;
});
if (resolversArray.length > 0) {
resolve(resolversArray);
} else {
reject("Resolvers Array is empty");
}
});
promise.then((array) => {
console.log("returned array", resolversArray);
});
Result is I get a rejected promise with Resolvers Array is empty being returned.

How to wait for the complete execution of a loop Node.js

I have such a loop :
var someArray = [];
for(var i = 0; i < myArray.length; i++) {
var tempArray = [];
arangodb.query('somequery')
.then(
cursor => cursor.all()
).then(
keys => tempArray = keys,
err => console.error('Failed to execute query:', err)
).then(function () {
someArray.push.apply(someArray, tempArray);
});
}
I want to do other operations when all tempArrays are collected in someArray. But since Node.js is async, I don't know how to do it. Can you help me with that? Thanks in advance.
This will result in a flat array of keys from cursor.all()
any arangodb.query that fails will be ignored (still with console output though)
Promise.all(myArray.map(item =>
arangodb.query('somequery')
.then(cursor => cursor.all()))
.catch(err => console.error('Failed to execute query:', err))
)
// remove rejections, which will be undefined
.then(results => results.filter(result => !!result))
// flatten the results
.then(results => [].concat(...results))
.then(results => {
// do things with the array of results
})
you need to use Promise.all()
var someArray = [];
function queryDB(){
return arangodb.query('somequery')
.then(
cursor => cursor.all()).then(
keys => tempArray = keys,
err => console.error('Failed to execute query:', err)
).catch(function(err){
console.log('Failed');
})
}
var promiseArray = [];
for(var i = 0; i < myArray.length; i++)
{
promiseArray.push(queryDB());
}
Promise.all(promiseArray).then(function(results){
someArray = results.filter(result => !!result);
})
basically queryDB() would return a promise, you can do Promise.all() to wait for all promises to resolve and then you can access the result in result
The only way to track if all your async operations are complete or not is to simply keep count of success callback triggers and failure callback triggers. Following should help you out.
let count = 0;
const checkCompletion = (curr, total) => {
if (curr < total) {
// Not all tasks finised
} else {
// All done
}
};
for(var i = 0; i < myArray.length; i++) {
var tempArray = [];
arangodb.query('somequery')
.then(cursor => cursor.all())
.then(keys => {
// success
count += 1;
checkCompletion(count, myArray.length);
}).catch(e => {
// failure
count += 1;
checkCompletion(count, myArray.length);
});
}

Categories

Resources