I was trying to execute multiple tasks asynchronously in node.js then save the return value into an object while putting all the tasks(promises) in a promise.all([]) but it doesn't seem to be working.
//A really minified function for convienince which sends a req to an api then
//assigns the response to the object that is passed in as a parameter
async function getAssessmentData(cookie, params,refObj){
const result = await fetch(
`https://www.sampleURL.com?` +
new URLSearchParams({
academicYearId: params.academic_year,
semesterId: params.semester,
courseId: params.course,
}),
);
//Assign the response to the object inside the parameter which i should have reference of
refObj = await result.json();
}
//I stringified the output object to display it here (consider it as a Javascript object)
let output= [
{
"meta":{
"academic_year":"2020/21",
"year":"Year III",
"semester":"One"
},
"assessment":[
{
"course_grade":"A",
"course_link_data":{
"academic_year":"f4c351be-8ed7-4197-9768-25d6e67c063e",
"semester":"11111111-1111-1111-1111-111111111111",
"course":"73adfaa1-0666-46a5-a2c4-3b0970d9025d"
},
"course_assessment":{
"assessments":[
],
"total":null
}
},
{
"course_grade":"B",
"course_link_data":{
"academic_year":"f4c351be-8ed7-4197-9768-25d6e67c063e",
"semester":"11111111-1111-1111-1111-111111111111",
"course":"6ab5fbe9-086e-46c8-b115-d0d9f19a98a3"
},
"course_assessment":{
"assessments":[
],
"total":null
}
}
],
"footer":{
"sgp":"0",
"sgpa":"0",
"cgp":"0",
"cgpa":"0",
"academicstatus":"Not determined yet!"
}
},
{
"meta":{
"academic_year":"2020/21",
"year":"Year III",
"semester":"One"
},
"assessment":[
{
"course_grade":"A",
"course_link_data":{
"academic_year":"f4c351be-8ed7-4197-9768-25d6e67c063e",
"semester":"11111111-1111-1111-1111-111111111111",
"course":"73adfaa1-0666-46a5-a2c4-3b0970d9025d"
},
"course_assessment":{
"assessments":[
],
"total":null
}
},
{
"course_grade":"B",
"course_link_data":{
"academic_year":"f4c351be-8ed7-4197-9768-25d6e67c063e",
"semester":"11111111-1111-1111-1111-111111111111",
"course":"6ab5fbe9-086e-46c8-b115-d0d9f19a98a3"
},
"course_assessment":{
"assessments":[
],
"total":null
}
}
],
"footer":{
"sgp":"0",
"sgpa":"0",
"cgp":"0",
"cgpa":"0",
"academicstatus":"Not determined yet!"
}
}
]
promiseList = [];
for (const element of output) {
for (const elementNested of element.assessment) {
promiseList.push(
getAssessmentData(identityCookie,elementNested.course_link_data,elementNested.course_assessment);
)
}
}
await Promise.all(promiseList);
console.log(JSON.stringify(output))
The course_link_data object is not changing.
From some debugging, I've found out that the getAssesmentData function is working correctly but I expect the problem is that the elementNested.course_assessment object I'm passing to the function from the loop is not from the "output" object.
since I'm going to send around 50 requests at once I thought I would do them asynchronously, is there a way to achieve this?
Related
Okay, I have literally no idea whats going on here. I'm assuming its some kind of reference issue? But I dont know how to get around it or whats causing it.
To sum it up, I have a list of objects, as well as an object that gets prepopulated to make sure I have data for all keys in the object.
I need to iterate over this list of objects, and by using the timeframeId in the metadata object, and the id in the data object, I want to assign the entire data object to the corresponding timeframeId and id hierarchy in the prepopulated object.
For some reason, all data properties are being overwritten to whatever the last row data is.
I've linked a repl so you can see for yourself: https://repl.it/#ThomasVermeers1/UnwrittenNoisyFirm#index.js
But my code is as follows:
const buildSegmentsFromRows = (rows, timeframeMetadata, defaultSegmentData) => {
// Prepopulate object to make sure every timeframe has a 'hello' key in it with some data
const emptySegments = timeframeMetadata.reduce((segmentMap, metadata) => {
segmentMap[metadata.timeframeId] = {
metadata,
segments: defaultSegmentData,
};
return segmentMap;
}, {});
// Now simply just loop over the rows, and set [row.metadata.timeframeId].segments[row.data.id] to row.data
const segments = rows.reduce((partialSegments, row) => {
const { timeframeId } = row.metadata;
const { id } = row.data;
/**
* This is the line where everything goes wrong
*/
partialSegments[timeframeId].segments[id] = row.data;
return partialSegments;
}, emptySegments);
return segments;
};
const rows = [
{
metadata: { timeframeId: '20202_01' },
data: {
'id': 'hello', 'value': 15
}
},
{
metadata: { timeframeId: '20202_02' },
data: {
'id': 'hello', 'value': 10
}
}
]
const timeframemetadata = [
{ timeframeId: '20202_01'},
{ timeframeId: '20202_02'}
]
const defaultSegmentData = {
'hello': {
'id': 'hello',
}
}
console.log(JSON.stringify(buildSegmentsFromRows(rows, timeframemetadata, defaultSegmentData), null, 2))
I'm expecting the end result to be:
{
"20202_01": {
"metadata": {
"timeframeId": "20202_01"
},
"segments": {
"hello": {
"id": "hello",
"value": 15
}
}
},
"20202_02": {
"metadata": {
"timeframeId": "20202_02"
},
"segments": {
"hello": {
"id": "hello",
"value": 10
}
}
}
}
But instead, value is getting set to 10 in all instances. I'm thinking its because we're setting the property to row.data, which is a reference, and gets updated on every call? But I'm at a complete loss here.
The problem is that you are referring to the same object for every segments in the list.
Therefore, changing the value of segments[id] will update defaultSegmentData, causing every reference to defaultSegmentData to change as well.
const emptySegments = timeframeMetadata.reduce((segmentMap, metadata) => {
segmentMap[metadata.timeframeId] = {
metadata,
segments: defaultSegmentData, // Everything goes wrong here.
};
return segmentMap;
}, {});
A simple solution to this problem is to avoid using the same reference to the object when creating the segmentMap:
const emptySegments = timeframeMetadata.reduce((segmentMap, metadata) => {
segmentMap[metadata.timeframeId] = {
metadata,
/** Or whatever default value you want.
* Just make sure to create a new instance of it for each call.
*/
segments: {},
};
return segmentMap;
}, {});
I'm using async map series to manipulate an array.
exports.MultiChannelInvoke = async function (bulkUploadArray) {
async.mapSeries(bulkUploadArray, async function (array, cb) {
let ap = array[0]
let apArray = ['PL', 'A', 'J', 'V', 'I'];
if (!apArray.includes(ap)) { // For Array's first value it is KL & it is getting inside this loop
//some manipulation
}
else {
//some manipulation
}
}, (err, results) => {
if (err) {
masterResult = { message: 'Failed During async Map', errorMessage: err }
} else {
let mongoData = batch;
uploadsummary.create(mongoData)
masterResult = { FinalResults: results }
}
})
return masterResult
}
My bulkupload Array will be like follow:
[
[
"KL",
{
"ms": "2147766904",
"desc": "APP-LIC JEEVAN POLICY,7878787878,30/01/19",
"ur": "DNDSER6911744",
"tap": "TA",
"tapupdate": [ "Array" ]
}
],
[
'PL',
{
ms: '3147766904',
desc: 'APP-LIC JEEVAN POLICY,9916957781,30/01/19',
ur: 'DNDSER6911745',
tap: 'TA',
tapupdate: [ Array ]
}
]
]
So as per my bulkupload array first value is KL & it is getting inside the first if loop.(which i have mentioned in comments in the code). So after getting inside & doing manipulation, it is not considering the second array value. After finishing the manipulation for first value, it is directly returning the result. Can anybody please tell me what Im doing wrong? Or should I not use async map series. Please suggest me.
First, making a function async-await doesn't auto-magically returns the value AFTER everything is processed. return masterResult will be executed before async.mapSeries you need to make a promise and return it.
Also, if you are using async-await inside async.js you need to return the data from the async function to be considered as result.
Here is a version incorporating what I told above:
const MultiChannelInvoke = function (bulkUploadArray) {
return new Promise((res, rej) => { //make promise
async.mapSeries(bulkUploadArray, async function (array) { //no cb required
let ap = array[0]
let apArray = ['PL', 'A', 'J', 'V', 'I'];
if (!apArray.includes(ap)) { // For Array's first value it is KL & it is getting inside this loop
return 'IF!'
}
else {
return 'ELSE';
}
}, (err, results) => {
if (err) {
rej({ message: 'Failed During async Map', errorMessage: err }); //reject
} else {
res(results); //resolve
}
})
})
}
MultiChannelInvoke([['KL',
{
ms: '2147766904',
desc: 'APP-LIC JEEVAN POLICY,7878787878,30/01/19',
ur: 'DNDSER6911744',
tap: 'TA',
tapupdate: [Array]
}],
['PL',
{
ms: '3147766904',
desc: 'APP-LIC JEEVAN POLICY,9916957781,30/01/19',
ur: 'DNDSER6911745',
tap: 'TA',
tapupdate: [Array]
}]]).then(console.dir);
Output:
[ 'IF!', 'ELSE' ]
I don't really know how to express what I want, but I'll try.
So, I have an object with an array inside with the name of recipes, that I receive from my API, and a valuePath which is an object:
Object
{
recipes: [
{
test: {
items: [
{
type: 'test1',
}
]
}
}
]
}
ValuePath
{
"allRecipes": {
"array": "recipes",
"values": {
"allTypes": {
"array": "test",
"values": {
"type": "type"
}
}
}
}
}
Briefly what I have to do, is iterate over the array recipes through out the valuePath, dynamically, because the array and the values can change. I don't really know how to explain it better and how to iterate thought deeply nested objects/array's having a valuePath as a reference to find the values.
What I've tried so far...
export const test = (object, valuePath) => {
for (const prop in valuePath) {
object = object[valuePath[prop].array]; // find the array
if (Array.isArray(object)) {
object.forEach(objRef => {
console.log('valueRef', objRef);
});
}
console.log('props->', valuePath[prop].values); // find the values
}
};
I think i need a recursion, but have no clue how to do one.
If I understood your problem, this could be an implementation...
If you run it with your data and path, it will return test1.
// INPUTS
const data = {
recipes: [
{
test: {
items: [
{
type: 'test1',
}
]
}
}
]
}
const path = {
"allRecipes": {
"array": "recipes",
"values": {
"allTypes": {
"array": "test",
"values": {
"type": "type"
}
}
}
}
}
// this is just an helper method for arrays...
Array.prototype.first = function () { return this[0] }
// this is an helper function that tells us whether
// a path object is still traversable.
// from what I understood, if it contains an `array` property
// we should follow it...
const isTraversable = path => !!path.array
// this is the actual implementation of the algorithm
const traverse = (data, path) => {
const nextPath = Object.values(path).first()
if ( isTraversable(nextPath) ) {
const array = data[nextPath.array]
// I noticed that at a certain point in the data object,
// we need to traverse an array, and in another it is an
// object with an `items` property.
// this next lines helps determine how go down
const nextData = Array.isArray(array) ? array.first() : array.items
// we recurse on the traversed data and path
return traverse(nextData, nextPath.values)
}
return data.first()[path.type]
}
console.log(traverse(data, path))
Please try this, I hope it will help you..
let obj = {
recipes: [
{
test: {
items: [
{
type: 'test1',
},
],
},
},
],
};
obj.recipes.forEach(test => {
test.test.items.forEach(items => {
console.log(items.type);
});
});
I'm trying to update a field in a MongoDB collection which has nested documents. I have to increase a certain value. The update query works just fine, but I need to nest it in another query where I get the current value, so I could increase it.
The nesting worked just fine when I used a faulty find() method. I realized I must use aggregate(). I can't get it working, the method returns undefined for some reason. I've tried the same aggregate query in the shell and it works, so it has to do something with the Node.js
The function that fails:
static addPointsToUser(mainId, userId, pointsToAdd) {
const db = getDb();
function getCurrent() {
db.collection('mycoll')
.aggregate([
{ $match: { _id: mainId } },
{ $unwind: '$userPoints' },
{ $match: { 'userPoints._id:': userId } },
{ $group: { _id: 'userPoints._id', userPoints: { $push: '$userPoints.points' } } }
])
}
function updateNew(newPoints) {
db.collection('mycoll')
.updateOne(
{ _id: mainId },
{ $set: { "userPoints.$[elem].points": newPoints } },
{
multi: true,
arrayFilters: [{ "elem._id": userId }]
}
)
}
return getCurrent()
.then(result => {
console.log(result);
const newPoints = result.userPoints[0];
return updateNew(newPoints)
.then(result => {
console.log(result);
return result;
})
})
}
The document looks like this:
{
"_id": ObjectId("5d4048f56a895612acabe0a9"),
// Some other fields
"userPoints": [
{ "_id": "manualID1", "points": 80 },
{ "_id": "manualID2", "points": 90 }
]
}
Expected aggregate result:
{ "_id" : "manualID1", "userPoints" : [ 90 ] }
Mongo shell gives the result seen above.
Actual result:
TypeError: Cannot read property 'then' of undefined
If I log the aggregate result it prints and empty array ( [] ).
Your methods getCurrent and updateNew are not returning anything. Which mean you are using .then() on something which is undefined as stated by your error message.
Adding a return statement before db.collection('mycoll') should help you with that.
I am retrieving a document from PouchDB in an Angular Service. The document is retrieved in the following format:
{
"_id":"segments",
"_rev":"1-4f0ed65cde23fe724db13bea1ae3bb13",
"segments":[
{ "name":"Aerospace" },
{ "name":"Auto repair" },
{ "name":"Commercial" },
{ "name":"Education" },
{ "name":"Energy" },
{ "name":"Farm/ranch" },
{ "name":"Furniture" },
{ "name":"Heavy Equipment" },
{ "name":"Hobbyist" },
{ "name":"Infrastructure" },
{ "name":"Luxury/Leisure" },
{ "name":"Military" },
{ "name":"MUP" },
{ "name":"Processing" },
{ "name":"Rail" },
{ "name":"Transportation" }
]}
And I want to map that to a new Array that would look like:
[
{ value: "Aerospace", viewValue: "Aerospace" },
{ value: "Auto Repair", viewValue: "Auto Repair" },
{ value: "Commercial", viewValue: "Commercial" }
...
]
To accomplish this, I have tried this code in my Service:
getSegments(): Observable<any[]> {
return from(this.database.get('segments'))
.pipe(
map((results) => results.segments)
);
}
And I transform the array in my Component like this:
segments: SegmentsLookup[] = [];
...
this.lookupDbService.getSegments()
.subscribe(data => {
data.forEach(element => {
this.segments.push({value: element.name, viewValue: element.name});
});
});
This works but I know there is a way to map this properly back in the Service code. Also, when done this way, the compiler complains about the "results.segments" stating "Property "segments" does not exist on type '{}'.
How do I map the data retrieved to the Array that I need in the Service's "getSegments" method?
You can do the transformation is 2 steps:
pipe/map to extract the segments
array/map to convert to the final data type
Please see an example here: https://stackblitz.com/edit/angular-ikb2eg?file=src%2Fapp%2Fapp.component.ts
let transformedData = observableData.pipe(
map(data => {
console.log(data, data.segments.length);
return data.segments.map(element => {
return { value: element["name"], viewValue: element["name"] };
});
})
);
transformedData.subscribe(data => {
this.mylist = data;
});
You can use the RxJS operator flatMap, which is a alias of mergeMap.
The official documentation describes flatMap as follows:
Projects each element of an observable sequence to an observable
sequence and merges the resulting observable sequences or Promises or
array/iterable into one observable sequence.
We can use flatMap and then the RxJS operator map like this:
flatMap to extract the segments
map to convert to the final data type
const transformedData = observableData.pipe(
flatMap(data => data.segments),
map(segment => ({
value: segment['name'],
viewValue: segment['name'],
})),
)
transformedData.subscribe(data => {
this.mylist = data;
});
Detailed explanation of how this works:
This article nicely explains how flatMap works and implements a version of flatMap, which works with arrays rather than RxJS observables, as follows:
function flatMap (arr, fn) {
return arr.reduce((flatArr, subArray) => flatArr.concat(fn(subArray)), [])
}
If we use this with the result of your database query we'll see we extract the segments.
const data = {
"_id": "segments",
"_rev": "1-4f0ed65cde23fe724db13bea1ae3bb13",
"segments": [
{ "name": "Aerospace" },
{ "name": "Auto repair" },
// ...
]};
const segments = flatMap([data], x => x.segments);
console.log(segments);
// > [
// > { "name": "Aerospace" },
// > { "name": "Auto repair" },
// > ...
// > ]
The RxJS flatMap operator returns an observable stream of segments, rather than an array of segments.
You can extend the map function and remove it from the component as follows :
map(result => {
result = result.segments;
let data = [];
result.forEach(element => {
data.push({value: element.name, viewValue: element.name});
});
return data;
});