Trying to render multiple API calls on the same page - node.js

I'm trying to render the results of two different API calls in a node app but am running into a problem.
Here are my functions:
A disc golf course API call:
function dgcrGetReq(res, cityName) {
var courseData = {
title: "courses",
courses: null
};
var config = {
method: 'get',
url: `${dgcrUrl}?key=${dgcrKey}&mode=findloc&city=${cityName}&state=ON&country=CA&sig=${locationSig}`,
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
};
axios(config)
//Await result here to collect data before sending weather request
.then(function (response){
courseData.courses = response.data;
console.log(courseData.courses);
res.render("courses", courseData);
}).catch(function (error){
console.log(error);
});
}
An open weather API call:
function getWeatherInfo(city) {
var weatherData = {
description: "",
temperature: "",
wind: ""
}
var config = {
method: 'get',
url: `https://api.openweathermap.org/data/2.5/weather?q=${city},ON,CA&appid=${weatherKey}`,
headers: { }
};
axios(config)
.then(function (response) {
weatherData.description = response.data.weather[0].description;
weatherData.temperature = (response.data.main.temp - 273.15 );
console.log(`temperature: ${weatherData.temperature}`)
console.log(JSON.stringify(response.data));
console.log(`Weather ${response.data.weather[0].main}`);
res.render( weatherData);
})
.catch(function (error) {
console.log(error);
});
}
Here is the render function:
app.get("/courses", (req, res) => {
var city = req.query.cityInput;
// console.log(city);
dgcrGetReq(res, city);
getWeatherInfo(city);
});
And here is the .pug component where the content is rendered:
extends layout
block layout-content
div.content
h1.page-title Course Listings
div#weather-info
h3 General Weather Information
p Description: #{weatherData.description}
p Temperature:
p Wind Speed:
each c in courses
div.course-container.flex-container
div.name-container.flex-container
a(href=`${c.dgcr_url}` target="_blank") <!-- Link to official DGCR page-->
h2 #{c.name}
p #{c.city}
div.detail-container
p Holes: #{c.holes}
p Average Rating:
img(src=`${c.rating_img}`)
I get the error line "Cannot read properties of undefined (reading 'description')".
If I remove the 'weatherData.description' line, then I get the error 'TypeError: Cannot read properties of undefined (reading 'app')'.
Any pointers here? Still getting used to using Node and API's.

There are multiple issues in your code:
getWeatherInfo calls res.render, but you don't pass res into the function (which you do with dgcrGetReq)
However, you can call res.render only once in response, because the first call to render ends the response.
So I recommend changing both getWeatherInfo and dgcrGetReq functions to return data instead of calling res.render on their own. You collect both responses and call res.render in the route handler (in the app.get("/courses", (req, res) => function).
Pay attention to the fact that both functions return a promise, so you need to wait for both promises to resolve and only then call res.render.
Sketch of solution (untested, there may be bugs):
// REMOVED res argument
function dgcrGetReq(cityName) {
// code removed for brevity
axios(config)
.then(function (response){
courseData.courses = response.data;
console.log(courseData.courses);
// HERE return those data
return courseData;
}).catch(function (error){
console.log(error);
});
}
function getWeatherInfo(city) {
// code removed for brevity
axios(config)
.then(function (response) {
weatherData.description = response.data.weather[0].description;
weatherData.temperature = (response.data.main.temp - 273.15 );
// HERE return data
return weatherData;
})
.catch(function (error) {
console.log(error);
});
}
app.get("/courses", (req, res) => {
var city = req.query.cityInput;
var renderData = null;
dgcrGetReq(city) // returns promise => use then
.then(courseData => {
renderData = courseData; // store data in outside scope so they are available later
return getWeatherInfo(city);
})
.then(weatherData => {
// add weatherData to single object which will be passed for rendering
renderData.weatherData = weatherData;
res.render("courses", renderData);
})
});
As additional improvements:
Add error handling (.catch) if dgcrGetReq or getWeatherInfo returns error
Use Promise.all to execute dgcrGetReq and getWeatherInfo independent of each other, to generate response more quickly
Avoid modifying the returned courseData (stored as renderData) object
Use async await

Related

return response data from async call

I created this function to get list all my drives from GDrive.
async getAllDrives(token) {
let nextPageToken = ""
let resultArray = []
const config= {
headers: {
Authorization: `Bearer ${token}`
}
};
const bodyParams = {
pageSize: 2,
fields: 'nextPageToken, drives(id, name)',
q:`hidden=false`,
};
do {
axios.get(
`https://www.googleapis.com/drive/v3/drives`,
config,
bodyParams,
).then(result => {
nextPageToken = result.data.nextPageToken;
resultArray.push(result.data.drives);
resultArray = resultArray.flat();
console.log("result", resultArray);
}).catch(error => {
console.log(error);
//res.send(error);
});
}while(nextPageToken);
resultArray = resultArray.flat();
resultArray.map(drive => {
drive.isSharedDrive = true;
return drive;
});
return JSON.stringify(resultArray);
}
When I look in console.log
then(result => {
nextPageToken = result.data.nextPageToken;
resultArray.push(result.data.drives);
resultArray = resultArray.flat();
console.log("result", resultArray);
})
I have the expected result,
result [
{
kind: 'drive#drive',
id: '**',
name: ' ★ 🌩'
},
]
but return JSON.stringify(resultArray); is empty.
I found a similar question here, How do I return the response from an asynchronous call? but the answer is not satisfying.
You used the async call slightly incorrectly. You calling axios.get without await keyword, but with .then chaining. Since you don't wait for result to return, you getting empty array first, returning you nothing. And only then your callback function inside .then is getting called. To simplify, you doing this in your example:
function getAllDrives() {
// Local variable where you want your result
let result = [];
// You calling the axios.get method, but don't wait for result
axios.get().then(result => {})
// Empty result is getting returned immediately
return result;
}
And when response is returned from the remote server, function inside .then trying to save result to local variable. But function is already completed, so you don't get anything.
What you actually should do is call axios.get with await keyword:
// You should always cover your asynchronous code with a try/catch block
try {
// Instead of `then` callback use `await` keyword. Promise returned from
// this method will contain result. If error occurs, it will be thrown,
// and you can catch it inside `catch`.
const result = await axios.get(
`https://www.googleapis.com/drive/v3/drives`,
config,
bodyParams
);
// Here is your code as you wrote it inside `then` callback
nextPageToken = result.data.nextPageToken;
resultArray.push(result.data.drives);
resultArray = resultArray.flat();
console.log("result", resultArray);
} catch (error) {
// And here is your error handling code as you wrote it inside `catch`
console.log(error);
}
This way your method will not complete until your request is not executed.
You can read more about async/await functions here.
I believe your goal is as follows.
You want to retrieve the drive list using axios.
Your access token can be used for retrieving the drive list using Drive API.
Modification points:
In order to use nextPageToken in the request, in this case, it is required to run the script with a synchronous process. So, async/await is used. This has already been mentioned in the existing answers.
When I saw your script, I thought that the query parameter might be required to be included in the 2nd argument of axios.get().
In order to use nextPageToken, it is required to include the property of pageToken. In your script, pageToken is not used. By this, the infinite loop occurs because nextPageToken is continued to be returned.
When these points are reflected in your script, how about the following modification?
Modified script:
let resultArray = [];
const config = {
headers: {
Authorization: `Bearer ${token}`,
},
params: {
pageSize: 2,
fields: "nextPageToken, drives(id, name)",
q: `hidden=false`,
pageToken: "",
},
};
do {
const { data } = await axios
.get(`https://www.googleapis.com/drive/v3/drives`, config)
.catch((error) => {
if (error.response) {
console.log(error.response.status);
console.log(error.response.data);
}
});
if (data.drives.length > 0) {
resultArray = [...resultArray, ...data.drives];
}
nextPageToken = data.nextPageToken;
config.params.pageToken = nextPageToken;
} while (nextPageToken);
resultArray.map((drive) => {
drive.isSharedDrive = true;
return drive;
});
return JSON.stringify(resultArray);
Testing:
When this script is run, the following result is obtained.
[
{"id":"###","name":"###","isSharedDrive":true},
{"id":"###","name":"###","isSharedDrive":true},
,
,
,
]
Note:
From the official document of "Drives: list",
pageSize: Maximum number of shared drives to return per page. Acceptable values are 1 to 100, inclusive. (Default: 10)
So, when pageSize is 100, the number of loops can be reduced. If you want to test the loop using nextPageToken, please reduce the value.
References:
axios
Drives: list
I recommend you study a little more about async/await.
It makes no sense for you to use async and put a .then().catch(), the purpose of async to get these encapsulated syntaxes.
async getAllDrives(token) {
try {
const getDrives = await this.request(token)
console.log(getDrives)
const results = this.resultArray(getDrives)
return results
} catch (e) {
console.log(e)
}
}
I didn't quite understand your while or your objective, adapt it to your code or remove it
async request(token) {
let nextPageToken = 1 // ????????
const config = {
headers: {
Authorization: `Bearer ${token}`
}
};
const bodyParams = {
pageSize: 2,
fields: 'nextPageToken, drives(id, name)',
q: `hidden=false`,
};
let getDrives = [];
// loop for each request and create a request array
for (let x = 0; x < fields.nextPageToken; x++) {
const request = axios.get(
`https://www.googleapis.com/drive/v3/drives`,
config,
bodyParams
);
getDrives.push(request)
}
const drives = await Promise.all(getDrives)
return drives
}
async resultArray(drivers) {
// result treatment here
}
The return of promise all will be an array of the driver's responses
Note: The response in request.data
const request = await axios.get()
const resposta = request.data
Read about
https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

Is It possible to call request with setInterval?

I'm new to node.js
I'm trying to call API with header every x seconds in node.js , And what i achieve is like
type some information of header for getting external API from client and post it to my backend server.
get data from External data which keeps changing data any second ,
call external API from backend server every seconds and send it to client.
get data from my backend server to client, and the data keep changing.
I'm not sure if the way I'm doing is okay .
, So, i tried to do like this:
In Node.js
app.post("/realtime", (req, res) => {
var url = req.body.GET_API_URL;
var header1_key = req.body.Headers1_key;
var header1_value = req.body.Headers1_Value;
var header2_key = req.body.Headers2_key;
var header2_value = req.body.Headers2_Value;
var header3_key = req.body.Headers3_key;
var header3_value = req.body.Headers3_Value;
var option = {
url: url,
method: "GET",
headers: {
[header1_key]: header1_value,
[header2_key]: header2_value,
[header3_key]: header3_value,
},
};
const interval = () => {
request(option, (error, response, body) => {
try {
res.json(JSON.parse(body));
} catch (error) {}
});
};
setInterval(interval, 1000);
});
in client
function getAPI() {
axios
.post("http://localhost:5000/realtime", {
GET_API_URL: convert.GET_API_URL,
Headers1_key: convert.Headers1_key,
Headers1_Value: convert.Headers1_Value,
Headers2_key: convert.Headers2_key,
Headers2_Value: convert.Headers2_Value,
Headers3_key: convert.Headers3_key,
Headers3_Value: convert.Headers3_Value,
})
.then(response => {
setRandom(response.data);
console.log(response);
})
.catch(error => {
console.log(error);
});
}
it doesn't get any error and it's didn't work as i expected.
So i wonder if what i do is right way to do or is complete wrong .
And if It's wrong I'd like to get advice .
if there is alternative to achieve it, i'd really appreciate it.
Thank you in advance
You can use Promise in javascript
function intervalAPI = async () => {
return new Promise((done, reject) => {
setIntrrval(() => {
try {
conse { data } = await axios(...);
done(data);
} catch (e) {
reject();
}
}, 1000);
}
}
Promise Docs

Returning body of a request function inside a variable

I have an endpoint in my node backend in which will need to retrieve for each item in my Adhoc collection from my local database the _id along with a number value which I need to calculate from the body of a request() function in an array of objects. The objects will be like this
{id: "id", sum: 3}
To do this I need to iterate through the Adhocs with a for loop and make a request for each to get the sum value and I need to be able to store these values before I have all of them and res.send() the array to the front end. I am having trouble storing the sum value in a variable. I have provided below the code of the request.
let theSum = request(options, function (error, response, body) {
if (error) throw new Error(error);
console.log(
'Response: ' + response.statusCode + ' ' + response.statusMessage
);
let bodyy = JSON.parse(body);
let sum = bodyy.fields.timetracking.originalEstimateSeconds / 3600 * theRate;
return sum;
});
I know this is wrong as the return statement is for the function inside the request function so it won't return the sum to my variable. And adding another callback function will basically be the same scenario. Anyone has any suggestions of how I can store the value from the request function so I can make further calls?
I found an answer that works for me pretty well. I didn't try Terry's answer above but I suppose that works as well since it's using Promise as well as me. What I've done is in a function I wrapped the request call in a Promise with a callback which is returned. Code below:
function asyncRequest (url) {
return new Promise (function (resolve, reject) {
var options = {
url: 'http://localhost:8080/rest/' + url,
auth: { username: 'username', password: 'password' },
headers: {
'Accept': 'application/json'
}
}
request(options, function (err, response, body) {
if (err) reject(err);
resolve(JSON.parse(body))
});
})
}
When I want to retrieve something I just have something like this:
let json = await asyncRequest('agile/1.0/issue/'+ adhoc[u].jIssue);
And that variable has the body of the request function inside and I can use it.
You can use async and await along with request-promise-native to loop over your objects and get the list of results you wish to have.
You can call the readEstimates function in a express.get( ).. handler as long as the handler is asynchronous (or you can use readEstimates().then(..)).
Now, we will wrap an error handler around the readEstimates call since, this could potentially throw an error.
For example:
const rp = require('request-promise-native');
async function readEstimates() {
const sumList = [];
for(const adhoc of adhocList) {
// Set your options here, e.g. url for each request.. by setting json to true we don't need to JSON.parse the body.
let options = { url: SOME_URL, json: true, resolveWithFullResponse: true };
let response = await rp(options);
console.log('Response: ' + response.statusCode + ' ' + response.statusMessage);
const sum = response.body.fields.timetracking.originalEstimateSeconds / 3600 * theRate;
sumList.push(sum);
}
return sumList;
}
async function testReadEstimates() {
try {
const sumList = await readEstimates();
console.log("Sumlist:", sumList);
} catch (error) {
console.error("testReadEstimates: An error has occurred:", error);
}
}
testReadEstimates();
You can also use readEstimates in an Express route:
app.get('/', async (req, res) => {
try {
const sumList = await readEstimates();
res.json({sumList}); // Send the list to the client.
} catch (error) {
console.error("/: An error has occurred:", error);
res.status(500).send("an error has occurred");
}
})

How am I suppose to stub a function which is dependent on result of previous function?

I have recently started writing tests and I don't have much experience.If any of the community member could point me in the right direction I would be really thankful. My scenario is simple I am half way through it but unable to solve my exact problem. Below is my code..
return generateServiceToken(req.body.appId, req.body.token, req.auth.userId)
.then(result => {
someService
.createCredentialsForUser(
req.auth.userId,
result.user.uid,
result.user.token
)
.then(result => {
return res.status(201).send(result);
});
})
.catch(error => {
return res.status(500).send({ error: `Credentials not valid - ${error}` });
});
The generateToken function is responsible to call a third party api to generate some credentials for their platform and return us the create credentials.
function generateServiceToken(appId: String, token: String, userId: String) {
return new Promise ((resolve, reject)=>{
const apiURL = `https://someapi.com/api/api.php?op=useradd&token=${token}&addr=${userId}&appid=${appId}`;
request.post(apiURL, (error, response, body) => {
const resp = JSON.parse(body);
if (resp.error) return reject(resp.error);
return resolve(resp);
});
});
}
Whereas, the someService.createCredentialsForUser function is responsible to save those credentials in database and return back the result in simple json format.
I am just stuck in stubbing someService.createCredentialsForUser function while writing the test case for happy-path
My test case is below..
describe.only("controllers/v3/some/", () => {
const c = {};
before(() => {
c.sandbox = sinon.createSandbox();
c.someServiceStub = c.sandbox
.stub(someService, "createCredentialsForUser")
.resolves(VALID_OUTPUT);
});
describe("when the request is valid", () => {
before(() => {
c.agent = setupApp(authenticationMiddleware(USER_ID));
return test(c, VALID_REQUEST_BODY);
});
it("should return 201", () => {
expect(c.response.statusCode).to.equal(201);
});
it("should call createCredentialsForUser", () => {
expect(c.stubs.createCredentialsForUser.called).to.equal(true);
});
});
});
The TestCase function is as follows..
function testCase(context, body = VALID_REQUEST_BODY) {
context.sandbox.resetHistory();
console.log(body.length);
const c = context;
return context.agent
.put(`/v3/some/`)
.send(body)
.then(r => {
c.response = r;
});
//.catch(err=>{c.response=err});
}
My someService.createCredentialsForUser function is responsible to save data into database I want to stub that that in a way that I could expect response return from generateServiceToken
I tried couples of ways which are as follows ..
First, I tried to stub that function in before() but no luck it fails with
error : IllegalArgumentError: init() must be called prior to use.
Second, I tried
c.response = c.sandbox.stub(someService, 'createCredentialsForUser').returns(Promise.resolve(r));
in my test function to stub with the value of resolved promise but no luck in this case it fails with the same error as mentioned above.

Wait for data from external API before making POST request

I'm using the IBM Watson Tone Analyser API with Express.js and React. I have this code which sends some test to the Watson API:
// tone-analyser.js
class ToneAnalysis {
constructor() {
const params = {
username: process.env.USERNAME,
password: process.env.PASSWORD,
version_date: '2018-01-31'
}
this.Analyzer = new ToneAnalyzerV3(params);
}
ToneAnalyser(input) {
let tones = this.Analyzer.tone(input, (err, tone) => {
if (err) console.log(err.message)
let voiceTone = tone.document_tone.tones[0].tone_id;
console.log(voiceTone) // Logs the right value on Node.js console
return voiceTone;
});
return tones;
}
}
module.exports = ToneAnalysis;
I then use this on my Express backend like so:
// server.js
const ToneAnalysis = require('./api/tone-analyser');
const app = express();
const input = {
tone_input: 'I am happy',
content_type: 'text/plain'
}
app.get('/api/tone', (req, res) => {
let tone = new ToneAnalysis().ToneAnalyser(input);
return res.send({
tone: tone
});
});
And I make an API call from React here:
// App.js
componentDidMount() {
this.callApi()
.then(res => {
console.log(res.tone); // Logs the wrong value on Chrome console
})
.catch(err => console.log(err));
}
callApi = async () => {
const response = await fetch('/api/tone');
const body = await response.json();
if (response.status !== 200) throw new Error(body.message);
console.log(body);
return body;
};
I expect the value of res.tone to be a string showing the tone gotten from the tone analysis function (new ToneAnalysis().ToneAnalyser(input);). Instead, I get
{
uri: {...},method: "POST", headers: {...}}
headers: {...},
uri: {...},
__proto__: Object
}
I think this happens because the res.send(...) runs before tone has a value from the API. My question is, how do I make res.send(...) run only after tone has a value?
I tried wrapping the callback function in this.Analyzer.tone(input, [callback]) in an async/await block, but that did not fix the issue. Any ideas on how to fix this will be highly appreciated. Thanks!
If the call to
let tone = new ToneAnalysis().ToneAnalyser(input);
returns a promise then you could do something like
tone.then(res.send.bind(res))
If the call to
let tone = new ToneAnalysis()`enter code here`.ToneAnalyser(input);
returns a promise then you could do something like
tone.then(res.send.bind(res))

Resources