Use async await with Array.map not working - node.js

I've used this approach, it should give me the Expected Output which I've mentioned below.
But, due to asynchronous execution, it is giving the Actual Output.
So, Please help me to solve the same.
See, I'm calling one async function, inside i'm running three nested map loops. And in the third loop I'm connecting the database and fetching values. Based on the value, I'm generating the values of the Object which is empty at the first. And once the function called, it is triggering the then method.That's where the problem arises, I need that then function to be executed after the called function executed completely. This is the complete problem statement
Given the following code:
let messageObject = {};
async function storeManager() {
// storing the defects or modifying
console.log('hii from storeManager()');
await Promise.all(
Object.keys(filledDefects).map((defectName) => {
Object.keys(filledDefects[defectName]).map((subDefectName) => {
Object.keys(filledDefects[defectName][subDefectName]).map(
async (zone) => {
const result = await dbConnectedPool.query(
`SELECT * FROM defect_table WHERE body_number=${enteredBodyNumber} AND category='${selectedCategory}' AND subcategory='${selectedSubCategory}' AND defect='${defectName}' AND subdefect='${subDefectName}' AND zone = ${zone.replace(
'_',
''
)}`
);
if (result.rows.length == 0) {
// block to save defects record for the first time
console.log(
`INSERT INTO defect_table (body_number,mode,category,subcategory,defect,subdefect,zone,defectCount,date,time,username) VALUES (${enteredBodyNumber},'${mode}','${selectedCategory}','${selectedSubCategory}','${defectName}','${subDefectName}',${zone.replace(
'_',
''
)},${
filledDefects[defectName][subDefectName][zone]
},'${date}','${time}','${username}');`
);
await dbConnectedPool.query(
`INSERT INTO defect_table (body_number,mode,category,subcategory,defect,subdefect,zone,defectCount,date,time,username) VALUES (${enteredBodyNumber},'${mode}','${selectedCategory}','${selectedSubCategory}','${defectName}','${subDefectName}',${zone.replace(
'_',
''
)},${
filledDefects[defectName][subDefectName][zone]
},'${date}','${time}','${username}');`
);
mod.set(
messageObject,
`Newly Saved Zone.${zone}.${defectName}.${subDefectName}`,
filledDefects[defectName][subDefectName][zone]
);
console.log('inside: ', messageObject);
} else {
// block to modify existing defect records
console.log(
`UPDATE defect_table SET defectCount=${
filledDefects[defectName][subDefectName][zone]
},date='${date}',time='${time}',username='${username}' WHERE body_number=${enteredBodyNumber} AND category='${selectedCategory}' AND subcategory='${selectedSubCategory}' AND defect='${defectName}' AND subdefect='${subDefectName}' AND zone=${zone.replace(
'_',
''
)}`
);
await dbConnectedPool.query(
`UPDATE defect_table SET defectCount=${
filledDefects[defectName][subDefectName][zone]
},date='${date}',time='${time}',username='${username}' WHERE body_number=${enteredBodyNumber} AND category='${selectedCategory}' AND subcategory='${selectedSubCategory}' AND defect='${defectName}' AND subdefect='${subDefectName}' AND zone=${zone.replace(
'_',
''
)}`
);
mod.set(
messageObject,
`Overwritten Zone.${zone}.${defectName}.${subDefectName}`,
filledDefects[defectName][subDefectName][zone]
);
console.log('inside: ', messageObject);
}
// checking whether already record exists with same aspects
}
);
});
})
);
console.log('bye from storeManager()');
}
storeManager().then(() => {
console.log('message outside:', messageObject);
});
Expected Output:
hii from storeManager()
bye from storeManager()
UPDATE defect_table SET defectCount=12,date='2022-10-12',time='12:52:33',username='Vasanth'
WHERE body_number=1234 AND category='LH SHELL BODY MAIN-LINE' AND subcategory='FENDER - LH
SBML' AND defect='Surface' AND subdefect='Dent' AND zone=210
inside: { 'Overwritten Zone': { _210: { Surface: [Object] } } }
UPDATE defect_table SET defectCount=12,date='2022-10-12',time='12:52:33',username='Vasanth'
WHERE body_number=1234 AND category='LH SHELL BODY MAIN-LINE' AND subcategory='FENDER - LH
SBML' AND defect='Surface' AND subdefect='Dent' AND zone=215
inside: {
'Overwritten Zone': { _210: { Surface: [Object] }, _215: { Surface: [Object] } }
}
message outside: {
'Overwritten Zone': { _210: { Surface: [Object] }, _215: { Surface: [Object] } }
}
Actuall Output:
hii from storeManager()
bye from storeManager()
message outside: {}
UPDATE defect_table SET defectCount=12,date='2022-10-12',time='12:52:33',username='Vasanth'
WHERE body_number=1234 AND category='LH SHELL BODY MAIN-LINE' AND subcategory='FENDER - LH
SBML' AND defect='Surface' AND subdefect='Dent' AND zone=210
inside: { 'Overwritten Zone': { _210: { Surface: [Object] } } }
UPDATE defect_table SET defectCount=12,date='2022-10-12',time='12:52:33',username='Vasanth'
WHERE body_number=1234 AND category='LH SHELL BODY MAIN-LINE' AND subcategory='FENDER - LH
SBML' AND defect='Surface' AND subdefect='Dent' AND zone=215
inside: {
'Overwritten Zone': { _210: { Surface: [Object] }, _215: { Surface: [Object] } }
}

You'll need to use Promise.all everywhere you are producing an array of promises, not just on the outermost call. And you'll need to make the map callbacks actually return those promises!
await Promise.all(Object.entries(filledDefects).map(async ([defectName, defect]) => {
await Promise.all(Object.entries(defect).map(async ([subDefectName, subDefect]) => {
await Promis.all(Object.entries(subDefect).map(async ([zoneName, zone]) => {
await …;
}));
}));
}));
Alternatively you can also write this without some of the async/await:
await Promise.all(Object.entries(filledDefects).map(([defectName, defect]) =>
Promise.all(Object.entries(defect).map(async ([subDefectName, subDefect]) =>
Promis.all(Object.entries(subDefect).map(async ([zoneName, zone]) => {
await …;
}));
));
));

Promise.all expects an array of promises as argument, but you pass it an array of undefined values instead. That means Promise.all will return a promise that is resolved immediately.
Two causes for this problem:
the outer map callbacks don't have a return statement (so they map each defectName and each subDefectName to undefined)
If you would return the result of the inner .map calls, then each defectName maps to an array (of arrays ...), which still isn't what you need. You don't want a nested array, but a flat array, so use return Object.keys().flatMap instead of Object.keys().map

Related

Return a node js response inside session.withTransaction

I am using session.withTransaction() to execute multiple updates in the mongo db. Please note that promiseArray has multiple Stock.update statements to update stock quantities.
await session.withTransaction(
async () => {
promiseResults = await Promise.all(promiseArray);
for (const result of promiseResults) {
recordCounter++;
if (result.nModified === 1) {
stockItemsNoUpdate.push(goodReturnSummary[recordCounter]);
}
}
if (stockItemsNoUpdate.length > 0) {
return res.status(200).send(response);
}
existingGoodReturnSummary = GoodReturn.build({
_id: sheetId,
goodReturnSummary,
agency,
createdBy,
});
await existingGoodReturnSummary.save({ session: session });
existingGoodReturnSummary = await GoodReturn.calculateTotalGoodReturnAmount(
existingGoodReturnSummary,
session
);
},
{
readPreference: 'primary',
readConcern: { level: 'local' },
writeConcern: { w: 'majority' },
}
);
If stockItemsNoUpdate.length > 0 I need to abort this transaction and send the response. done by below code segment.
if (stockItemsNoUpdate.length > 0) {
return res.status(200).send(response);
}
But I cannot do this because of the below error
Any idea on how to resolve this ??
Cheers
See Nodejs mongodb's Transaction API `withTransaction` always return null and https://jira.mongodb.org/browse/NODE-2014.
https://jira.mongodb.org/browse/NODE-2014?focusedCommentId=2420255&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-2420255 provides a workaround.

Elasticsearch node js point in time search_phase_execution_exception

const body = {
query: {
geo_shape: {
geometry: {
relation: 'within',
shape: {
type: 'polygon',
coordinates: [$polygon],
},
},
},
},
pit: {
id: "t_yxAwEPZXNyaS1wYzYtMjAxN3IxFjZxU2RBTzNyUXhTUV9XbzhHSk9IZ3cAFjhlclRmRGFLUU5TVHZKNXZReUc3SWcAAAAAAAALmpMWQkNwYmVSeGVRaHU2aDFZZExFRjZXZwEWNnFTZEFPM3JReFNRX1dvOEdKT0hndwAA",
keep_alive: "1m",
},
};
Query fails with search_phase_execution_exception at onBody
Without pit query works fine but it's needed to retrieve more than 10000 hits
Well, using PIT in NodeJS ElasticSearch's client is not clear, or at least is not well documented. You can create a PIT using the client like:
const pitRes = await elastic.openPointInTime({
index: index,
keep_alive: "1m"
});
pit_id = pitRes.body.id;
But there is no way to use that pit_id in the search method, and it's not documented properly :S
BUT, you can use the scroll API as follows:
const scrollSearch = await elastic.helpers.scrollSearch({
index: index,
body: {
"size": 10000,
"query": {
"query_string": {
"fields": [ "vm_ref", "org", "vm" ],
"query": organization + moreQuery
},
"sort": [
{ "utc_date": "desc" }
]
}
}});
And then read the results as follows:
let res = [];
try {
for await (const result of scrollSearch) {
res.push(...result.body.hits.hits);
}
} catch (e) {
console.log(e);
}
I know that's not the exact answer to your question, but I hope it helps ;)
The usage of point-in-time for pagination of search results is now documented in ElasticSearch. You can find more or less detailed explanations here: Paginate search results
I prepared an example that may give an idea about how to implement the workflow, described in the documentation:
async function searchWithPointInTime(cluster, index, chunkSize, keepAlive) {
if (!chunkSize) {
chunkSize = 5000;
}
if (!keepAlive) {
keepAlive = "1m";
}
const client = new Client({ node: cluster });
let pointInTimeId = null;
let searchAfter = null;
try {
// Open point in time
pointInTimeId = (await client.openPointInTime({ index, keep_alive: keepAlive })).body.id;
// Query next chunk of data
while (true) {
const size = remained === null ? chunkSize : Math.min(remained, chunkSize);
const response = await client.search({
// Pay attention: no index here (because it will come from the point-in-time)
body: {
size: chunkSize,
track_total_hits: false, // This will make query faster
query: {
// (1) TODO: put any filter you need here (instead of match_all)
match_all: {},
},
pit: {
id: pointInTimeId,
keep_alive: keepAlive,
},
// Sorting should be by _shard_doc or at least include _shard_doc
sort: [{ _shard_doc: "desc" }],
// The next parameter is very important - it tells Elastic to bring us next portion
...(searchAfter !== null && { search_after: [searchAfter] }),
},
});
const { hits } = response.body.hits;
if (!hits || !hits.length) {
break; // No more data
}
for (hit of hits) {
// (2) TODO: Do whatever you need with results
}
// Check if we done reading the data
if (hits.length < size) {
break; // We finished reading all data
}
// Get next value for the 'search after' position
// by extracting the _shard_doc from the sort key of the last hit
searchAfter = hits[hits.length - 1].sort[0];
}
} catch (ex) {
console.error(ex);
} finally {
// Close point in time
if (pointInTime) {
await client.closePointInTime({ body: { id: pointInTime } });
}
}
}

How to return a list of objects from Cypress Custom Commands in type script

I am using Cypress for my end to end Integration tests. I have a use case which involves returning a list of objects from Cypress Custom Commands and I have a difficulty in doing so. Here is my code pointer:
index.ts
declare global {
namespace Cypress {
interface Chainable<Subject> {
getTestDataFromElmoDynamoDB({locale, testType}): Cypress.Chainable<JQuery<expectedData[]>> // ??? not sure what return type should be given here.
}
}
}
Cypress.Commands.add('getTestDataFromDynamoDB', ({locale, testType}) => {
// expectedData is an interface declared. My use case is to return the list of this type.
let presetList: expectedData[]
cy.task('getTestDataFromDynamoDB', {
locale: locale,
testType: testType
}).then((presetData: any) => {
presetList = presetData;
// the whole idea here is to return presetList from cypress task
return cy.wrap(presetList) //??? not sure what should be written here
})
})
sampleSpec.ts
describe('The Sample Test', () => {
it.only('DemoTest', () => {
cy.getTestDataElmoDynamoDB({
locale: env_parameters.env.locale,
testType: "ChangePlan"
}).then((presetlist) => {
// not sure on how to access the list here. Tried wrap and alias but no luck.
presetList.forEach((preset: expectedData) => {
//blah blah blah
})
})
})
})
Did anyone work on similar use case before?
Thanks,
Saahith
Here My own command for doing exactly that.
Cypress.Commands.add("convertArrayOfAlliasedElementsToArrayOfInteractableElements", (arrayOfAlliases) => {
let arrayOfRecievedAlliasValues = []
for (let arrayElement of arrayOfAlliases) {
cy.get(arrayElement)
.then(aelement =>{
arrayOfRecievedAlliasValues.push(aelement)
})
}
return cy.wrap(arrayOfRecievedAlliasValues)
})
The way I do it is to pass it in an array and cy.wrap the array, Because it lets you chain the command with an interactable array.
The key point is - it has to be passed as array or object, because they are Reference types, and in cypress it is hard to work with let/var/const that are value types.
You can also allias the cy.wrapped object if you like.
The way to use it in code is:
cy.convertArrayOfAlliasedElementsToArrayOfInteractableElements(ArayOfElements)
What you asked for can be implemented as follows, but I do not know what type expectedData is, so let's assume that expectedData:string [], but you can replace string[] with your type.
plugins/index.ts
module.exports = (on: any, config: any) => {
on('task', {
getDataFromDB(arg: {locale: string, testType: string}){
// generate some data for an example
const list: string[] = [];
list.push('a', 'b');
return list;
},
});
};
commands.ts
declare global {
namespace Cypress {
interface Chainable<Subject> {
getTestDataElmoDynamoDB(arg: {locale: string, testType: string}): Cypress.Chainable<string[]>
}
}
}
Cypress.Commands.add('getTestDataElmoDynamoDB', (arg: {locale: string, testType: string}) => {
let presetList: string[] = [];
cy.task('getDataFromDB', arg)
.then((presetData?: string[]) => {
expect(presetData).not.be.undefined.and.not.be.empty;
// if the data is incorrect, the code will break earlier on expect, this line for typescript compiler
if (!presetData || !presetData.length) throw new Error('Present data are undefined or empty');
presetList = presetData;
return cy.wrap(presetList); // or you can return cy.wrap(presetData)
});
});
db.spec.ts
describe('Test database methods', () => {
it('When take some test data, expect that the data was received successfully ', () => {
cy.getTestDataElmoDynamoDB({ locale: 'someEnvVar', testType: 'ChangePlan' })
.then((list) => {
expect(list).not.empty.and.not.be.undefined;
cy.log(list); // [a,b]
// You can interact with list here as with a regular array, via forEach();
});
});
});
You can also access and receive data from cy.task directly in the spec file.
describe('Test database methods', () => {
it('When take some test data, expect that the data was received successfully ', () => {
cy.task('getDataFromDB', arg)
.then((list?: string[]) => {
expect(list).not.be.empty.and.not.be.undefined;
cy.log(list); // [a,b] — the same list as in the version above
});
});
});

NodeJS String.replace() problem while filtering

I can't modify the filtering parameter with
String.replace()
I can get the filtering keys from the URL as an object but its badly fromatted from me.
Filtering: {{URL}}/api/v1/bootcamps?averageCost[lt]=10000
Current format: { averageCost: { lt: '10000' } }
Right fromat: { averageCost: { $lt: '10000' } }
So I tried to convert it as a String and replace that value. But that value can be: lt, lte, gt, gte, in but it has some problem because the line after the .replace() method doesnt executed and of course the catch block cathes the error...
My code snippet:
try {
console.log(req.query);
const queryStr = JSON.stringify(req.query);
console.log(queryStr); //thats the last thing I get
queryStr = queryStr.replace(
/\b(gt|gte|lt|lte|in)\b/g,
match => `$${match}`
);
console.log(queryStr); // I dont get this
const bootcamps = await Bootcamp.find();
res.status(200).json({
succes: true,
count: bootcamps.length,
data: bootcamps
});
} catch (err) {
return res.status(404).json({ succes: false });
}
To replace it correctly you should use something like this:
let queryStr = '{ "averageCost": { "lt": "10000" }, "test": { "gt": "12345"} }';
const regex = /\b(gt|gte|lt|lte|in)\b/g;
queryStr = queryStr.replace(regex, '$$' + "$1"); // <-- here is the correct replace
This will replace queryStr with:
{ "averageCost": { "$lt": "10000" }, "test": { "$gt": "12345"} }
JSFiddle https://jsfiddle.net/c52z8ewr/
If you need the object back just do JSON.parse(queryStr)
You can also Try This
const queryCpy = { ...this.query };
// console.log(queryCpy, 'before filter');
console.log(queryCpy, 'after filter');
let queryString = JSON.stringify(queryCpy);
queryString = queryString.replace(
/\b(gt|gte|lt|lte)\b/g,
(rep) => `$${rep}`,
);
console.log(queryString, 'after filter');
If want an object in return do:
const bootcamps = await Bootcamp.find(JSON.parse(queryStr));

Order of function execution in Node async/await

I have a doubt in making sure the functions run in sequence and the result from the first call is used in second call.
DB Function
async runquery(){
try {
...
const results = await db.statementExecPromisified(statement, []);
return results;
} catch (e) {
console.log("Error - " +JSON.stringify(e));
return e;
}
}
Group id
async function groupByID(approvers) {
const group = _.groupBy(approvers, 'ID');
return Object.keys(group).map(ID=> {
return group[ID].reduce((approvers, cur, idx) => ({
...approvers,
['NAME' + (idx + 1)]: cur.ID,
}), { ID});
})
Final Function
async function preparePayload() {
if(levels.length != 0 ){
let statement= `SELECT * FROM ITEMS `
list = await runquery(statement) ;
id = await groupByID(list) ;
}
let result={}
result.ID=id;
}
Output from DB : [{ID:1,NAME:'F1'},{ID:2,NAME:'F2'},{ID:1,NAME:'F3'}]
Expected Output : [{ID:1,NAME1:'F1',NAME2:'F2'},{ID:2,NAME:'F2'}]
But the output is not coming as expected.
[{
"ID": "2"
},
{
"ID": "1"
}]
I am guessing this is due to sequence of the function execution because when i do console.log(id) works as expected but when i assign to the result variable it gives unexpected output.
I am not sure if I put the question correctly .Kindly let me know if it needs to be more detail.
I don't see anything wrong with your code apart from two small changes
groupByID doesn't need to be async and hence don't need to be awaited
['NAME' + (idx + 1)]: cur.ID, needs to be changed to ['NAME' + (idx + 1)]: cur.NAME,
For simplicity I have removed the db call and replaced it with a promise which returns the result which you have mentioned in your post.
See the working code here - https://repl.it/repls/PepperyGrossForms
It gives me this result
{
ID: [ { ID: '1', NAME1: 'F1', NAME2: 'F3' }, { ID: '2', NAME1: 'F2' } ]
}
Hope this helps.

Resources