How to use async map series in nodejs? - javascript

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' ]

Related

JavaScript, fetch, API

i want to do the following: get a random name with fetch from this website https://swapi.dev/api/people/, which i did and i can see it in my html page then i want also to get a random planet, here i need to access the homeworld key, and to return the link, before returning the link i formatted to get a random url and from this one i also have to show the name of the planet on my page. The first fetch works fine, at least i think but the 3rd .then() is not working or at least i don't know how to access the information from the homeworld url. This is my first time trying fetch() and it will be nice if you guys can help me telling where i did wrong in code and maybe different solutions but not so complicated :D tnks
let randomNumber = Math.floor(Math.random()*9)
const fetchPromise = fetch("https://swapi.dev/api/people/");
let test
let test2
let planets = document.querySelector('#age')
fetchPromise
.then((response) => {
if (!response.ok) {
throw new Error(`Http error: ${response.status}`);
}
return response.json();
})
.then((json) => {
console.log(json.results[randomNumber].name)
showRandomUserData(json)
test = json.results[0].homeworld
test = test.slice(0, -2)
// console.log(test + randomNumber + "/");
// console.log(test + "/" + randomNumber + "/");
test = test + randomNumber + "/";
return fetch(test)
// return fetch("https://swapi.dev/api/planets/2/");
})
.then(response => response.json()).then(json =>
{ test2=json.name
console.log(test2);
planets.innerHTML = test2
})
showRandomUserData = (randomUser) => {
document.querySelector("#name").innerHTML =
randomUser.results[randomNumber].name;
}
Solved
Here's a simple solution that uses fetch() to grab data from both those URLs and then insert all the people and the one planet that is returned into your web page:
function myFetch(...args) {
return fetch(...args).then(response => {
if (!response.ok) {
throw new Error(`fetch failed with status ${response.status}`);
}
return response.json();
});
}
Promise.all([
myFetch("https://swapi.dev/api/people/"),
myFetch("https://swapi.dev/api/planets/2/")
]).then(([people, planet]) => {
const peopleDiv = document.getElementById("people");
let peopleHTML = "";
for (let p of people.results) {
peopleHTML += `<div>${p.name}</div>`;
}
peopleDiv.innerHTML = peopleHTML;
const planetDiv = document.getElementById("planets");
let planetHTML = `<div>${planet.name}</div>`;
planetDiv.innerHTML = planetHTML;
}).catch(err => {
console.log(err);
});
<div id="people"></div>
<hr>
<div id="planets"></div>
As for using the results, the people URL returns a structure that looks like this:
{
count: 82,
next: 'https://swapi.dev/api/people/?page=2',
previous: null,
results: [
{
name: 'Luke Skywalker',
height: '172',
mass: '77',
hair_color: 'blond',
skin_color: 'fair',
eye_color: 'blue',
birth_year: '19BBY',
gender: 'male',
homeworld: 'https://swapi.dev/api/planets/1/',
films: [Array],
species: [],
vehicles: [Array],
starships: [Array],
created: '2014-12-09T13:50:51.644000Z',
edited: '2014-12-20T21:17:56.891000Z',
url: 'https://swapi.dev/api/people/1/'
},
{
name: 'C-3PO',
height: '167',
mass: '75',
hair_color: 'n/a',
skin_color: 'gold',
eye_color: 'yellow',
birth_year: '112BBY',
gender: 'n/a',
homeworld: 'https://swapi.dev/api/planets/1/',
films: [Array],
species: [Array],
vehicles: [],
starships: [],
created: '2014-12-10T15:10:51.357000Z',
edited: '2014-12-20T21:17:50.309000Z',
url: 'https://swapi.dev/api/people/2/'
}
}
So, you have people.results which is an array and you can access people.results[n] to get an item from that array. That item will be an object which has properties like .name, .height, etc...
The specific planet URL you show returns a single planet object like this:
{
name: 'Alderaan',
rotation_period: '24',
orbital_period: '364',
diameter: '12500',
climate: 'temperate',
gravity: '1 standard',
terrain: 'grasslands, mountains',
surface_water: '40',
population: '2000000000',
residents: [
'https://swapi.dev/api/people/5/',
'https://swapi.dev/api/people/68/',
'https://swapi.dev/api/people/81/'
],
films: [
'https://swapi.dev/api/films/1/',
'https://swapi.dev/api/films/6/'
],
created: '2014-12-10T11:35:48.479000Z',
edited: '2014-12-20T20:58:18.420000Z',
url: 'https://swapi.dev/api/planets/2/'
}
So, you access properties on that object as in planet.name.
Notice that the people results are paged. There are 82 total results, but only 10 come in this first result. The rest come with results for other pages such as https://swapi.dev/api/people/?page=2.
Similar to this answer but using async/await to avoid callback hell. If you can, try using this approach. Why?
Excellent recommendation in that answer by jfriend00 to use Promise.all instead of separate fetch calls, as that enables fetching to happen in parallel. To know more.
sandbox to test and try
const fetchData = async (...args) => {
try {
const response = await fetch(...args);
return response.json();
} catch (err) {
throw new Error(`fetch failed with status ${err?.message}`);
}
};
const updateDOM = (people, planet) => {
document.getElementById("people").innerHTML =
people.results.reduce((s, p) => s + `<div>${p.name}</div>`, "");
document.getElementById("planets").innerHTML = `<div>${planet.name}</div>`;
};
const populateData = async () => {
try {
const [people, planet] = await Promise.all([
fetchData("https://swapi.dev/api/people/"),
fetchData("https://swapi.dev/api/planets/2/"),
]);
// do stuff with 'people' or 'planet'
// example, get
// const firstPersonsHomeworld = people.results[0].homeworld;
// console.log(firstPersonsHomeworld);
// or
// const planetName = planet.name;
// console.log(planetName);
updateDOM(people, planet);
} catch (err) {
// errorHandler(err);
console.error(err);
}
};
// start app
populateData();

How to excute a task asynchronously in node.js?

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?

How do I dynamically add method to javascript object without getting this weird error

I have an array of javascript objects,
let headers = [
{
text: 'something',
value: 'something else'
},
{
text: 'something1',
value: 'something else1'
},
// etc..
]
I want to loop through the array and add a method to each object in the array like so (ignore "this", I'm using vue):
this.headers.map(h => {
h['filter'] = function (value) {
if (!this.filterValue) {
return true;
}
return value.toLowerCase().includes(this.filterValue.toLowerCase());
}
});
It looks fine to me, but when my loop completes, the function does not work, and I think it has something to do with this error in "arguments" and "caller":
Any ideas on how to resolve this error?
the key thing is this, you need to learn more about this in JS, and it is asked in interviews a lot, run this snippet and figure how this is going
function A(headers = [{
text: 'something',
value: 'something else'
},
{
text: 'something1',
value: 'something else1'
},
// etc..
], filterValue = 'else1') {
this.filterValue = filterValue
this.headers = headers.map(h => ({
...h,
filter: () => {
if (!this.filterValue) {
return true;
}
return h.value.toLowerCase().includes(this.filterValue.toLowerCase());
}
}))
}
console.log(new A().headers.map(x => x.filter()))

For loop with Node js promise chaining

I am very new to Node js and asynchronous programming seems difficult for me to grasp. I am using promise-mysql to make the flow synchronous but I have hit a road block with for loop inside of a chain of promise
I have a multiple choice question module. One table stores all the mcq questions and the other stores all the related choices for the questions. I am using the output of the first query as an input to the second query and so I did promise chaining as below
var mcqAll=[]
var sql_test_q_ans='select qId, q_text from questions'
con.query(sql_test_q_ans)
.then((result)=>{
for(var i=0; i<result.length; i++)
{
ques=result[i]
var sql_test_q_ops='SELECT op_text, op_id FROM mc_ops WHERE
q_id='+result[i].q_id
con.query(sql_test_q_ops)
.then((resultOps)=>{
mcqAll.push({i: ques, ops: resultOps})
console.log(mcqAll)
})
}
})
I am trying to create a javascript object array which would look something like this
[{q_text:'How many states in USA', q_ops:{1:25, 2:35, 3:45, 4:50}}
{question2 and its options}
{question3 and its options}....
]
When I run the above code the object populates all the question's options correctly but the same question is repeated in all the q_text for all questions.
[ { q_text: 'No of states in USA',
[ {op_text: '25', mc_op_id: 113 },
{ op_text: '35', mc_op_id: 114 },
{ op_text: '45', mc_op_id: 115 },
{ op_text: '50', mc_op_id: 116}],
{ q_text: 'No of states in USA',
[ {op_text: 'A', mc_op_id: 1 },
{ op_text: 'B', mc_op_id: 2 },
{ op_text: 'C', mc_op_id: 3 },
{ op_text: 'D', mc_op_id: 4}],
{ q_text: 'No of states in USA',
[ {op_text: 'Yes', mc_op_id: 31 },
{ op_text: 'No', mc_op_id: 32 },
{ op_text: 'No sure', mc_op_id: 33 },
{ op_text: 'Might be', mc_op_id: 34}]
]
I feel like it has something to do with asynchronous flow since console.log before the second query gets printed in all before printing anything after the second query. Any insight would be appreciated
Edit: I added a sample output for better understanding. As seen in the output, the options change and get stored in the js object in the for loop but the question is updated for all the objects to the last question in the for loop
node js current working async and await, still now use to async and await,
use this reference url: https://javascript.info/async-await
async and await is work as promise, await is use to wait to execute script
example
let mcqAll=[]
let sql_test_q_ans='select qId, q_text from questions'
async function showAvatar() {
let result = await con.query(sql_test_q_ans);
if(result.length > 0){
array.forEach((async function (item, index, result) {
let q = result[index];
let sql_test_q_ops='SELECT op_text, op_id FROM mc_ops WHERE
q_id='+result[index].q_id
let executeQuery = await con.query(sql_test_q_ops);
if(executeQuery.affectedRows > 0){
mcqAll.push({index: q, ops: executeQuery})
console.log(mcqAll);
}
});
}
}
You have a scope problem here
This is an example to reproduce your problem:
ques is a global variable that is updated in the for-loop so, when the async code ends the execution will read the global variable with the last ques = result[i] value.
'use strict'
const result = ['a', 'b', 'c']
const mcqAll = []
var ques
for (var i = 0; i < result.length; i++) {
ques = result[i]
var sql_test_q_ops = 'SELECT op_text, op_id FROM mc_ops WHERE q_id = ' + result[i].q_id
query(sql_test_q_ops)
.then(() => {
mcqAll.push({ i: ques })
console.log(mcqAll)
})
}
function query() {
return new Promise(resolve => setTimeout(resolve, 100))
}
But, if you simply declare the ques like this:
for (var i = 0; i < result.length; i++) {
const ques = result[i]
const sql_test_q_op...
all will work.
It is a good practice to use const or let instead of var because the last one creates a global scoped variable that is dangerous.
Regarding your comment: the output is empty because this for-loop is sync, so you reply in sync way to the response.
An example on how to manage this case could be like this:
'use strict'
const result = ['a', 'b', 'c']
const mcqAll = []
const promiseArray = result.map(ques => {
const sql_test_q_ops = 'SELECT op_text, op_id FROM mc_ops WHERE q_id = ' + ques.q_id
return query(sql_test_q_ops)
.then(() => { mcqAll.push({ i: ques }) })
})
// Wait for all the query to complete before rendering the results
Promise.all(promiseArray)
.then(() => {
console.log({ mcqAll });
res.render('mcqAllPage', { mcqAll })
})
.catch(err => res.send(500)) // this is an example
function query() {
return new Promise(resolve => setTimeout(resolve, 100))
}
Consider that there are many possibilities to implement this:
use for async iterator to run query sequentially
improve performance by run only one query with a in condition instead of a query for each q_id and manage the result with some code to group the results
using the promise array as in the example
Go deeper and choose the one that fits best for your need.
Important: .catch always the promise chain!

Rxjs Map Array in Json Document to new Array type

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;
});

Categories