async inside a for loop nodejs - arrays

I am trying to access an api and I will have to run the api calls several times based on the page numbers I need to iterate, the following is the code which I am using and how can I get the all the response pushed into an array.
as nodeJs is single threaded It is not waiting for the responses from the api.
How can I can tackle this and ensure all the response values are being pushed into an array
Inside the for loop I want the final array which has all the values of the api response. So, I check the total page value and response page Number if that matches which means that will be the last page and I push the array to another function but when I do that it does not have all the values because nodejs does not wait for the api response.
const fs = require('fs');
var pepKey = 'asdfasdfasd';
var pepResponse;
var pepTimecards = [];
pep();
function pep(){
var options = {
headers: {
"content-type": "application/json",
},
agentOptions: {
pfx: fs.readFileSync('./certificate/asdfsdaf.p12'),
passphrase: 'asdasdsda'
}
};
request.get('https://source.asdfasdf.io/api/organisations/asdfasdf/timecard_keys?timecard_type=Flex',options, (err, res, body) => {
if (err) { return console.log(err); }
pepResponse = JSON.parse(body)
pepTimecards = pepResponse.data;
if(pepResponse.pages > 1){
for(let i=2;i<=pepResponse.pages;i++){
var url = 'https://source.`pepme`.io/api/organisations/sdfsadf/timecard_keys?timecard_type=Flex&page='+pageNo;
request.get(url,options, (err, res, body) => {
if (err) { return console.log(err); }
body = JSON.parse(body)
pepTimecards = pepTimecards.concat(body.data)
if(pepResponse.pages == body.page){
console.log(pepResponse.pages)
console.log(body.page +"body page")
console.log(pepTimecards)
}
});
}
}else{
}
});
}

Use the request-promise library which supplies promisified versions of the request library. Then, you can use async/await in your for loop to serialize your operations:
Newer answer to go with the edited code in the OP's question
const fs = require('fs');
const rp = require('request-promise');
const pepKey = 'asdfasdfasd';
pep().then(pepTimecards => {
// the timecard data is valid in here
console.log(pepTimecards);
}).catch(err => {
console.log(err);
});
async function pep() {
let timecards = [];
const options = {
headers: {
"content-type": "application/json",
},
agentOptions: {
pfx: fs.readFileSync('./certificate/asdfsdaf.p12'),
passphrase: 'asdasdsda'
},
json: true,
uri: 'https://source.asdfasdf.io/api/organisations/asdfasdf/timecard_keys?timecard_type=Flex'
};
let pepResponse = await rp(options);
timecards = pepResponse.data;
if (pepResponse.pages > 1) {
for (let i = 2; i <= pepResponse.pages; i++) {
options.uri = 'https://source.`pepme`.io/api/organisations/sdfsadf/timecard_keys?timecard_type=Flex&page='+pageNo;
let body = await rp(url, options);
// add body.data onto the existing array
timecards.push(...body.data);
}
} else {
}
console.log(pepResponse.pages)
console.log(timecards)
return timecards;
}
Prior Answer before OP edited the code in their question:
const rp = require('request-promise');
// I'm assuming this is some sort of method definition on a class, otherwise it needs the function keyword
async pageno(pageNo) {
for (let i=2;i<=pepResponse.pages;i++){
try {
options.uri = 'https://test/timecard_keys?timecard_type=asdas&page='+pageNo;
// let request-promise parse the json for you automatically
options.json = true;
let body = await rp(options);
pepTimecards = pepTimecards.concat(body.data)
if (pepResponse.pages == body.page){
console.log(pepResponse.pages)
console.log(body.page +"body page")
console.log(pepTimecards)
}
} catch(e) {
// decide what to do for error handling
// this will log and rethrow so the caller will get a rejected promise
console.log(e);
throw e;
}
}
// return some value here to be the resolved value of the returned promise
return pepTimecards;
}
In your code, it is not clear where the options, pepTimecards, pepResponse variables are declared. They should probably be declared as local variables here or passed in to the function and/or returned from your function.
Summary of modifications:
Add async to method declaration so we can use await.
Load request-promise library into rp variable
Add options.json = true to the let the request-promise library parse the JSON result for us automatically
Change rp() to just use the options structure (add URL to that)
Add try/catch to catch any errors from the await, log them, then rethrow so pageno() will return a promise that rejects if there is an error (you can customize the behavior when there's an error if desired)
Add a return value so there is meaningful resolved value to the promise (you should not be using side-effect programming as it is now (modifying variables that are not passed in, declared locally or returned).
Things for you still to fix:
Stop using side-effect programming where you modify free variables that aren't passed in, aren't declared locally and aren't returned. This is a bad way to design code. You don't show enough overall context from the calling code or where these other variables are defined to make a concrete recommendation on how it should be done.
Decide what your error handling strategy is if there's an error on one of the requests and implement that strategy and proper handling.

Related

how intercept and stub the response of a rpc call in react with cypress

I want to intercept a rpc call that I made to the api in my react app. I'm using a custom hook that receives the buffer and the rpc method that I want to call and returns the data(something like react-query useQuery hook).
The thing is because of being a rpc call, the request urls of my requests are all the same and the response is binary, I can't distinguish the requests and intercept the one to stub.
One example of making a rpc call:
const {response, loading, error} = useRpc({
Buffer: GetUser,
Request: GetUserRequest
});
Edit 1:
I'm using
cy.fixture('fixutre-file').then((data) => {
const response = new TextDecoder().decode(res.body);
cy.intercept('https://example.com/', { method: 'POST' },
(req) => {
req.continue((res) => {
if ("some condition for distinguishing the request I want to intercept, here") {
res.send({ fixture: 'fixutre-file' });
}
});
});
}):
to get the response and decide whether or not intercept this req and instead send back my fixture data. But the response constant is still some unreadable string. What's wrong with my approach?
Edit 2:
Another approach that I used, was to use the cypress-protobuf package and encode my fixture.json file with the related protobuffer file:
cy.fixture('fixutre-file').then((data) => {
cy.task('protobufEncode', {
fixtureBody: data,
message: 'the_message',
protoFilePath: './protobuf/protofile.proto'
}).then((encodedData) => {
cy.intercept('https://example.com/', { method: 'POST' },
(req) => {
/////// approach 1(didn't work): ///////
// can't use this approach(because there is no identifier on
// req object to distinguish the requests I want to
// intercept)
// if ("some condition based on `req` here") {
// req.reply(encodedData);
// } else {
// req.continue();
// }
/////// approach 2: ///////
// using encodedData to compare it with res.body
req.continue(res => {
// can't compare res.body with encodedData, because
// encodedData is an empty string!
});
}).as('needToWait');
cy.wait('#needToWait').get('some selector').should('exist')
});
}):
Now the problem is:
encodedData is just an empty string, meaning it didn't work, so I can't compare the response with my fixture data to intercept the related request
You can simply check for some value from the request that distinguishes it from the other requests. Request bodies and headers are often good places to start. Additionally, you can use req.alias to conditionally assign an alias if you need to wait for that specific call.
cy.intercept('/foo', (req) => {
if (req.body.bar === true) { // or whatever logic indicates the call you want to intercept
req.alias = 'baz'; // conditionally assign alias
req.reply({foo: 'bar'}); // mock response
} else {
req.continue(); // do not mock response
}
});
cy.get('something')
.click()
.wait('#baz'); // waits for your specific 'baz' call to happen.

How does Meteor methods return results?

I am using meteor/react for learning facebook graph api.
I want to access users' post on facebook timeline and display them on screen. How can that be done?
With the guidance of the solution provided here [How to perform common FB actions using Meteor?. I have tried the following code: server.js
Meteor.methods({
'seePost' : function(){
var graph=Npm.require('fbgraph');
if(Meteor.user().services.facebook.accessToken){
graph.setAccessToken(Meteor.user().services.facebook.accessToken);
var future = new Future();
var onComplete = future.resolver();
graph.get('/me/feed',function(err,result) {
console.log(result);
return onComplete(err,result);
})
Future.wait(future);
}
else{
return false;
}
}
});
client side code :
Meteor.call("seePost", function(err,result) {
if(err) console.log("error" , err);
else console.log("RES", result);
});
I expect the result displayed in the client side console since I want to show the users the posts on his/er timeline, But I get following output :
RES, undefined
You can do it using await and Meteor.callAsync
Basically the client code waits for the call to complete, and gives you the returned data
const result = await Meteor.callAsync("seePost");
Errors should be handled with a try..catch block
If you use fibers/future, you need to return something with "future".
const future = new Future();
// some code getting result or something
future.return(something);
return future.wait();
this will return something in the callback from client call.
try this code, when you're using fibers you need to "wait" for the response
Meteor.methods({
'seePost': function () {
var graph = Npm.require('fbgraph');
if (Meteor.user().services.facebook.accessToken) {
graph.setAccessToken(Meteor.user().services.facebook.accessToken);
var future = new Future();
var onComplete = future.resolver();
graph.get('/me/feed', function (err, result) {
console.log(result);
if (err) {
return future.return(false);
} else {
return future.return(result);
}
})
return future.wait();
}
return false;
}
});

Node.js beginner struggling with arrays, promises and Async

A node.js (and coding in general) beginner here, struggling with the async nature of node. I'm trying to write some code that will look up the members of certain AD groups and add the member names to an array, as per the "getMembers" function below. I'm only interested in computer objects, which is why I only have ad.find returning "other" objects.
Once that is complete, I want the "processAssets" function to do something with the array - for the purpose of testing, just ouptutting to the console.log. The problem is that "processAssets" is running before "getMembers" has populated the array. What am I doing wrong? I realise the answer may begin with "several things"...!
const ActiveDirectory = require('activedirectory');
var ad = new ActiveDirectory(config);
var query = 'memberOf=cn=';
var cNames = [
'group1',
'group2',
'group3'
];
var baseOu = ',ou=Groups,dc=example,dc=com';
function run(cNames) {
Promise.all(cNames.map(cName => getMembers(cName))).then(processAssets())
}
async function getMembers(cName) {
await ad.find(query + cName + baseOu, async function(err, results) {
if ((err) || (! results)) {return;}
await _.each(results.other, function(other) {
assetArray.push(other.cn);
});
});
}
function processAssets() {
console.log("Contents of assetArray (" + assetArray.length + " assets):");
assetArray.forEach(function(item) {
console.log(item);
});
}
thanks in advance.
You have some things mixed up.
The main problem causing your immediate issue is this line:
Promise.all(cNames.map(cName => getMembers(cName))).then(processAssets())
You need to pass a function to then() which will be called when the promise resolves. You are not doing that, you are passing it the result of calling processAssets(), which has the effect of calling processAssets() immediately. Typically you would us something like:
Promise.all(cNames.map(cName => getMembers(cName))).then(() => processAssets())
/* ^^ pass a function */
Additionally you are await things for no purpose. There's no reason to await here:
await ad.find(query + cName + baseOu, async function(err, results) {
ad.find doesn't return a promise. In general functions that take callbacks don't return promises (maybe there are some exceptions, but I can't think of any). If you want to have a promise to use in run()'s Promise.all you need to wrap the find function in a promise and return it. Something like:
function getMembers(cName) {
return new Promise((resolve, reject) => {
ad.find(query + cName + baseOu, function(err, results) {
if (err) return reject(err)
// I'm making some assumptions about results. But hopefully this gives
// a good enough idea
let data = results.other.map(other => other.cn)
resolve(data)
});
});
}
Now getMembers returns a promise that resolves to the result of ad.find and you can use it in `Promise.all.

Troubles with Promises

I'm doing an Ionic project and I'm getting a little bit frustrated whit promises and '.then()' although I've read a lot of documentation everywhere.
The case is that I have one provider with the functions loadClients and getWaybills.
The first one gets all the clients that have waybills and the second one gets all the waybills from one concrete client.
loadClients() {
return new Promise(resolve => {
this.http.get('http://localhost/waybills?fields=descr1_sped&idUser='+ this.id)
.map(res => res)
.subscribe(data => {
this.data = data.json();
resolve(this.data);
});
});
}
// GET WAYBILLS
getWaybills(client) {
return new Promise(resolve => {
this.http.get('http://localhost/waybills/?stato=0&idUser='+ this.id +'&descr1_sped='+ client)
.map(res => res)
.subscribe(data => {
this.data = data.json();
resolve(this.data);
});
});
}
On the other hand, on the component welcome.ts I have a function loadWaybills which is called on the view load and is executing the following code, my idea is to get all the clients and then get the respective waybills of each one. Then I'll take just of the ones that are defined.
The problem is that on the second .then() instead of getting the variable data I'm getting just undefined... I've understood that if you put a synchronous code inside .then() can be properly executed and work with the "data" which is the result of the promise. Why am I getting this undefined?
loadWaybills() {
//We first load the clients
this.waybills.loadClients()
.then(data => {
this.waybill = data;
var preClients = this.waybill;
this.clients = [];
//Here we're deleting duplicated clients and getWaybills of them)
for (let i = 0; i < preClients.length; i++) {
if (this.clients.indexOf(preClients[i].descr1_sped) == -1) {
this.waybills.getWaybills(preClients[i].descr1_sped)
.then(data => {
**//Here we'll check if the clients has waybills or not**
this.clientWaybills[i] = data;
this.clients.push(preClients[i].descr1_sped)
});
}
}
});
}
It is hard to say because we don't know what the API is meant to return. For example, there may be a missing field somewhere from the first GET and now for the second one, it returns as undefined sometimes. If it only returns undefined sometimes, a simple solution to this, would be to check that the value is defined before assigning it to the variable.
If it always returns as undefined and shouldn't, try to debug the code and make sure that the values are present before the second .then.

mapping the response to corresponding request

I am making $http request to multiple environment and processing after I get all the responses. I am using the code below:
$q.all(Object.keys($rootScope.envs).map(request)).then(function(res){
var results = {};
for (var env in res) {
results[env] = res[env].data;
}
}, function(err){
console.error(err);
});
function request(env) {
return $http.get(callService.getDomainUrl()+'/'+$rootScope.envs[env]+ '/hosts.json');
}
The above code works fine, but the results object looks like below:
{
0: {data:{}},
1: {data:{}},
2: {data:{}},
3: {data:{}}
}
I want the corresponding response for each key and the results should be like
{
env1: {data:{//data for env1}},
env2: {data:{//data for env2}},
env3: {data:{//data for env3}},
env4: {data:{//data for env4}},
}
How to map the corresponding response to the key? Please let me know how to get this as this is asynchronous request. Should I have something from the API to know which env the API is coming from?
I think the simplest way would be to push the result handling into the request function, that way you still have the 'env' value in scope.
var results = {};
$q.all(Object.keys($rootScope.envs).map(request)).then(function(res){
// Do something with 'results' here.
}, function(err){
console.error(err);
});
function request(env) {
return $http.get(callService.getDomainUrl()+'/'+$rootScope.envs[env]+ '/hosts.json')
.then(function(res) { results[env] = res.data; return env; });
}
Another option would be to replace my return env with return [env, res.data] and then you can go back to creating the results object as in your original code.
The important thing here is to remember you can handle the $http.get promises individually as well as using the promises from the call to then in $q.all.

Resources