Node.js beginner struggling with arrays, promises and Async - arrays

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.

Related

React: Method finishing before data loaded

I am trying to retrieve some data from Yahoo Finance using an XHTML Request, which works. However, I am trying to display the data retrieved on my app, but the method to retrieve the data is returning "undefined" before the data has been loaded.
async componentDidMount() {
var tempData = await this.fetchAsync();
console.log(tempData)
this.handleLoad(tempData)
}
handleLoad = (num) => {
this.setState(state => ({
price: num
}));
}
async fetchAsync () {
const xhr = new XMLHttpRequest();
xhr.withCredentials = true;
const {params} = this.props.navigation.state;
var ticker = params.ticker;
var result;
var tempArray = [1];
var url = "https://yahoo-finance-low-latency.p.rapidapi.com/v8/finance/spark?symbols=" + ticker + "&range=2y&interval=1d"
xhr.addEventListener("readystatechange", function () {
if (this.readyState === this.DONE) {
result = JSON.parse(this.responseText);
tempArray = result[ticker]['close'];
testPrice = tempArray[tempArray.length-1]
console.log(testPrice)
var self = this;
return tempArray[tempArray.length-1]
}
});
xhr.open('get', url, true);
xhr.setRequestHeader("x-rapidapi-key", "my key");
xhr.setRequestHeader("x-rapidapi-host", "yahoo-finance-low-latency.p.rapidapi.com");
xhr.send();
}
I am using the componentDidMount() function to begin calling the methods to load the data, but when the app renders, the values are not displayed.
As you can see inside the fetchAsync() method, I return the value I need, but when I try and console.log the return from this method, I get undefined.
I have also tried moving this return to the end of the method, but when I use console.log here to ensure that tempArray has the data I need, it is empty.
I need to display tempArray[tempArray.length-1] on my screen, but the data is not loaded in time, and does not update even after it has loaded.
Your return tempArray[tempArray.length-1] inside the fetchAsync isn't actually returning from fetchAsync -- it's just returning from the callback function inside addEventListener. In fact, you don't actually have any code that is taking advantage of the async tag you have on that function.
One solution to this would be to call handleLoad directly from inside fetchAsync instead of return tempArray. (Of course, you'll want to make sure that you've bound this correctly to handleLoad).
Another solution would be to pass a callback function into fetchAsync that you could call instead of returning. Then, at your call site, it might look something like this:
this.fetchAsync((tempData) => {
console.log(tempData)
this.handleLoad(tempData)
});
Finally, a third solution would be to switch from XMLHTTPRequest to fetch, and then you could take advantage of async/await and actually make that fetchAsync method async (and be able to return a value from it).

How to wait for getDownloadURL to finish in my mapping function before updating my object array in react state?

getImages() {
const entries_copy = this.state.entries;
entries_copy.map(entry => {
storage.refFromURL(entry.sign_in_photo).getDownloadURL()
.then((url) => {
entry["inPhotoURL"] = url;
storage.refFromURL(entry.sign_out_photo).getDownloadURL()
.then((url) => {
entry["outPhotoURL"] = url;
});
}).catch((error) => {
// Handle any errors
});
});
this.setState({entries: entries_copy});
}
I'm trying to retrieve the download url for images and store them in my entry object inside my entries object array but the problem I'm facing right now is that the setState is called before the urls are retrieved and I have no idea how to wait for it to complete before setting the state. I have searched for similar problems but most of them are solved by executing it inside then() but for mine, I can't execute it inside then() because I have to wait for all the entries to be updated. I have only recently started using React for this project so I'm sorry if the answer is obvious.
This is because the code in asynchronous.
You should call setState inside the .then() function.
I would recommend you to read about Promises in Javascript. They are an important aspect of the language to master.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
In addition to the answer of #TomSlutsky, note that you need to correctly chain your promises and you should not forget to "always return results, otherwise callbacks won't catch the result of a previous promise".
So you need to do as follows:
storage.refFromURL(entry.sign_in_photo).getDownloadURL()
.then((url) => {
entry["inPhotoURL"] = url;
return storage.refFromURL(entry.sign_out_photo).getDownloadURL()
})
.then((url) => {
entry["outPhotoURL"] = url;
this.setState(...);
})
.catch((error) => {
// Handle any errors
});
Note also how the catch() method is called at the end of the chain, see the doc for more details (and possible other options).

Exporting an array within an ".then" doesnt work

I'm new to NodeJS and are only familiar with Java. I'm trying to create a file that creates objects based on a database and adds them to an array. This array I want to be able to export so that I can use it throughout the whole program, but when I try to export the array it doesn't work. I've tried googling and understanding but haven't come across anything that was helpful unfortunately.
I hope that someone can help me understand
I've tried calling module.exports after the ".then" call, but it just returns an empty array because its async.
I've also tried calling module.exports = teams inside the .then call but it didn't work neither.
var teams = [];
function assignTeamsToClasses() {
return new Promise((resolve, reject) => {
getAllTeamsInDb((teamList) => {
teamList.forEach((aTeam) => {
let newTeam = new Team(aTeam['teamid'], aTeam['teamname'], aTeam['teamrank']);
teams.push(newTeam);
});
resolve();
});
})
}
assignTeamsToClasses().then(() => {
module.exports = teams;
});
main.js
var teams = require('./initialize.js');
console.log(teams);
I expect it to return all teams that are in the database. I know that array is not empty when called within the ".then" call, but the export part does not.
Simple
the sequence require() + console.log() is synchronous
assignTeamsToClasses() is asynchronous, i.e. it updates teams at some unknown later point in time.
You'll have to design your module API to be asynchronous, e.g. by providing event listener interface or Promise interface that clients can subscribe to, to receive the "database update complete" event.
A proposal:
module.exports = {
completed: new Promise(resolve =>
getAllTeamsInDb(teams => {
const result = [];
teams.each(aTeam =>
result.append(new Team(aTeam.teamid,
aTeam.teamname,
aTeam.teamrank)
)
);
resolve(result);
})
),
};
How to use it:
const dbAPI = require('./initialize.js');
dbAPI
.completed
.then(teams => console.log(teams))
.catch(error => /* handle DB error here? */);
Every caller who uses this API will
either be blocked until the database access has been completed, or
receive result from the already resolved promise and proceed with its then() callback.

async inside a for loop nodejs

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.

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.

Resources