I'm trying to get a server to make a series of API calls according to a list of parameters obtained from a form. However, with the async http get method or the npm got library I cannot get my code to await for the responses of the API calls before it tries to render the response. My code looks like this:
router.post('/getPolicies', async function(req, res, next){
let issnList = req.body.issn_list
issnList = issnList.split(/\r?\n/)
await getPolicies(issnList);
res.render("policy", {data:policies})
});
async function getPolicies(issns){
for (let i = 0; i<issns.length; i++){
url = "some_url"+ issns[i]
const {data} = await got.get(url).json();
constructDataSet(issns[i], data)
}
}
function constructDataSet (issn, data){
//...
//get some information out of a json and construct a new json with data in needed format
}
The error I get is "Cannot read properties of undefined" because it's trying to read properties of the "data" json when it hasn't gotten it yet.
I think its because you are traversing the list synchronously when it should be asynchronous. Below are two diff implementation, the for await loop believe was made available in es6 and the second would be an alternative implementation.
router.post('/getPolicies', async function(req, res, next){
let issnList = req.body.issn_list
issnList = issnList.split(/\r?\n/)
await getPolicies(issnList);
res.render("policy", {data:policies})
});
// Prefer
async function getPolicies(issns){
for await (const issns of issns){
let url = "some_url" + issn;
const {data} = await got.get(url).json();
constructDataSet(issns, data)
}
}
async function getPoliciesV2(issns) {
await forEach(issns, (issn) => {
let url = "some_url" + issn;
const {data} = await got.get(url).json();
constructDataSet(issns, data);
});
}
async function forEach(data, callback) {
for(let i = 0; i < data.length; i++) {
callback(data[i]);
}
}
function constructDataSet (issn, data){
//...
//get some information out of a json and construct a new json with data in needed format
}
Related
I want to fetch subject Contents based on subject code and then inside each subject content, I want to fetch its sub contents as well and then store the main contents and sub contents in one array as object and return the data to react.
Please help me with this.
Node express API code
app.post('/api/teacher/courses/maintopics', (req, res) =>
{
let SubCode = req.body.data;
let teacher = new Teacher();
teacher.getCoursesMainContent(SubCode).then(result =>
{
let Contiants = [];
result.forEach(element =>
{
SubContent = [];
element.forEach(e =>
{
let contentCode = e.ContentCode;
teacher.getCoursesSubContent(contentCode).then()
.then(res => {
SubContent.push(res)
// here I want to store the sub content inside SubContent array
});
})
});
res.json(Contiants);
});
});
the problem is that when res.json(Contiants); is executed, the promises (getCoursesSubContent) are not resolved yet.
you need to use await like jax-p said.
also note that you cannot use forEach with await/promises (well you can, but it wont work as you wish it does : Using async/await with a forEach loop)
app.post('/api/teacher/courses/maintopics', async (req, res, next) =>
{
try {
let SubCode = req.body.data;
let teacher = new Teacher();
const results = await teacher.getCoursesMainContent(SubCode);
let Contiants = [];
for (let element of results) {
SubContent = [];
for (let e of element) {
let contentCode = e.ContentCode;
let res = await teacher.getCoursesSubContent(contentCode);
SubContent.push(res)
}
}
res.json(Contiants);
} catch(err) {
next(err);
}
});
I am trying to process signup data for a uni project . I am using basic koa modules and I am not allowed to use express, ideally I want to get the data inside the variable post. I want to process the data for example to see if the password has less than 5 characters , if so i would like that the program would not redirect the user to different address but if no errors occur i would like the program to redirect to regOk.html, I tried many other ways like initializing the variable outside of ctx.req.on but none were successful . Can anyone help me ?
export async function postregister(ctx) {
let bodyString = "";
ctx.req.on("data", (chunk) => {
bodyString += chunk;
});
//let collectData = new Array();
ctx.req.on("end", () => {
var post = querystring.parse(bodyString);
var email = post["email"];
var password = post["password"];
var passbestätigen = post["passwort bestä"];
var vorname = post["vorname"];
var nachname = post["nachname"];
var adresse = post["adresse"];
var stadt = post["stadt"];
var telefonnummer = post["telefonnummer"];
var geburtsdatum = post["geburtsdatum"];
var regData = model.add(ctx.db, post);
regData.then(() => console.log("singup successful"))
});
await ctx.render("regOk.html");
}
I'm not very familiar with koa, but I believe your issue is related to the order in which your code is executed.
The event in charge of parsing the data received in the body of the request ends after the synchronic execution of your postregister method, so you never get to see the value of post in the order you'd expect.
One possible solution to go around this issue would be wrapping the parsing of data in a promise, waiting for that promise to complete, and executing then and catch functions once the processing is done.
export async function postregister(ctx) {
await new Promise((resolve) => {
let bodyString = "";
ctx.req.on("data", (chunk) => {
bodyString += chunk;
});
ctx.req.on("end", async () => {
resolve(querystring.parse(bodyString));
});
})
.then(async (post) => {
await model.add(ctx.db, post)
.then(async () => {
console.log("singup successful");
await ctx.render('regOk.html');
});
})
.catch(async (error) => {
console.error(error);
await ctx.render('error.html');
});
}
This way, you handle body parsing inside the Promise, and after that completed you get the result of querystring.parse(bodyString) as a variable named post in your then handler.
I am trying to process response data and do not make next request before current data didn't processed. I tried use async/await and generators.
Generator:
private *readData() {
const currentUrl = this.getUrl();
const requestSettings = this.getRequestSettings();
yield axios.get( currentUrl, requestSettings).then( (response: any) => {
console.log('Make request');
return this.getData(response);
});
}
*readItem() {
let responseData: any;
if (!responseData) {
const response = this.readData();
responseData = response.next();
}
console.log('res data:', responseData['value']);
yield responseData['value'].then((res: any) => {
return res;
});
}
and then in the main code I do next:
for (let i=0;i<10;i++) {
item = transport.readItem().next();
console.log("R:", item);
}
Another idea was using async/await
async readItems() {
const headers = this.settings.headers;
const response = await axios.get( url, {
headers: headers
});
return response.data;
}
But in this case I get a promise in the response, if I just try to call this method 10 times.
I read 10 items, but I still make 10 requests to the server. Is it possible make one request, processed 10 items, and then make the second request? Maybe I have to use another pattern or whatever.
Async/await is right approach, just put await in front of readItem() and the Promise you get will be awaited that will give you desired. If your loop is in top level use readItem().then(). The latest NodeJS version allows await in top level.
for (let i=0;i<10;i++) {
item = await transport.readItem();
console.log("R:", item);
}
I found next solution
(async () => {
for (let i = 0; i < 10; i++) {
item = await transport.readItem();
console.log("R:", item);
}
})();
because I ran this part of code inside script without any functions/methods
My code:
var price = {};
function getPrice(price) {
const https = require('https');
var item = ('M4A1-S | Decimator (Field-Tested)')
var body = '';
var price = {};
https.get('https://steamcommunity.com/market/priceoverview/?appid=730&market_hash_name=' + item, res => {
res.on('data', data => {
body += data;
})
res.on('end', () => price ['value'] = parseFloat(JSON.parse(body).median_price.substr(1))); //doesnt add to dict
}).on('error', error => console.error(error.message));
}
price['test'] = "123" //adds to dict fine
getPrice(price)
console.log(price);
Output:
{ test: '123' }
as you can see, the "test: 123" gets added, but the "value: xxx" from the function doesn't. Why is that?
There are two main problems here:
You're redeclaring the variable inside your function so you're declaring a separate, new variable and modifying that so the higher scoped variable, never gets your .value property.
You're assigning the property inside an asynchronous callback that runs sometime later after your function has returned and thus your function actually returns and you do the console.log() too soon before you have even obtained the value. This is a classic issue with return asynchronously obtained data from a function in Javascript. You will need to communicate back that data with a callback or with a promise.
I would also suggest that you use a higher level library that supports promises for getting your http request and parsing the results. There are many that already support promises, already read the whole response, already offer JSON parsing built-in, do appropriate error detection and propagation, etc... You don't need to write all that yourself. My favorite library for this is got(), but you can see a list of many good choices here. I would strongly advise that you use promises to communicate back your asynchronous result.
My suggestion for fixing this would be this code:
const got = require('got');
async function getPrice() {
const item = 'M4A1-S | Decimator (Field-Tested)';
const url = 'https://steamcommunity.com/market/priceoverview/?appid=730&market_hash_name=' + item;
const body = await got(url).json();
if (!body.success || !body.median_price) {
throw new Error('Could not obtain price');
}
return parseFloat(body.median_price.substr(1));
}
getPrice().then(value => {
// use value here
console.log(value);
}).catch(err => {
console.log(err);
});
When I run this, it logs 5.2.
You're actually console.logging .price before you're setting .value; .value isn't set until the asynchronous call fires.
You are declaring price again inside the function and also not waiting for the asynchronous task to finish.
const https = require("https");
const getPrice = () =>
new Promise((resolve, reject) => {
const item = "M4A1-S | Decimator (Field-Tested)";
let body = "";
return https
.get(
`https://steamcommunity.com/market/priceoverview/?appid=730&market_hash_name=${item}`,
res => {
res.on("data", data => {
body += data;
});
res.on("end", () =>
resolve(
parseFloat(JSON.parse(body).median_price.substr(1))
)
);
}
)
.on("error", error => reject(error));
});
const main = async () => {
try{
const price = await getPrice();
//use the price value to do something
}catch(error){
console.error(error);
}
};
main();
I still confused about how to use promises. I have a for loop call an asynchronous method which returns a value. I use this value to push into an array. But when I print the array it is empty. Here is what I did:
async function getLink(link) {
var browser = await puppeteer.launch({headless: true});
const page = await browser.newPage();
await page.goto(LINK)
const result = await page.evaluate( async() => {
let data = [];
const $ = window.$;
$('#gallery_01 .item').each(function(index, product) {
data.push($(product).find('a').attr('data-image'));
});
return data;
});
await browser.close();
return result;
}
var final = [];
for (var i = 0; i < 10; i++) {
var data = getLink(value[i].url).then(function(data) {
console.log(data); // urls show here
final.push(data);
});
}
Promise.all(final).then(() => {
console.log(final) // empty
})
The final show empty. What did I do wrong with Promise? Pls help!
I can't see what value is, but it looks like it's supposed to be an array of objects with a url property?
Assuming the getLink() function is okay, try this for your loop:
const final = [];
for (var i = 0; i < 10; i++) {
final.push(getLink(value[i].url));
}
Promise.all(final)
.then(data => {
console.log(data);
});
Or a slightly more compact way of accomplishing the same thing:
const promises = value.map(v => getLink(v.url));
Promise.all(promises)
.then(data => {
console.log(data);
});
Update: My bad, got a bit confused. The following code would only work without () => after the var fn
You are very close. Try this:
var final = [];
var results = []; // you need a separate array for results
for (var i = 0; i < 10; i++) {
// renamed the variable, changed 'data' to 'fn'
var fn = () => getLink(value[i].url).then(function(data) {
console.log(data); // urls show here
results.push(data);
});
final.push(fn);
}
Promise.all(final).then(() => {
console.log(results)
})
Promise.all accepts an array of promises. You have an array 'final' but seem to try to store the result of the fucntion execution as well as the function itself.
To do this correctly - first get an array of promises. Then pass them to Promise.all().
P.S. Assuming your function actually works, haven't looked at it, since the question was about promises.