Wait for data from external API before making POST request - node.js

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

Related

Trying to render multiple API calls on the same page

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

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

NodeJS: How to use Promise.all in order to execute a method once multiple requests are executed?

I am having a NodeJS project, where I have array of N parameters and I need to call an endpoint N times (once with each parameter). Then I need to listen once all the responses are returned and to execute a certain function only once all the results are retrieved.
I know how to implement the above in front-end frameworks, where I am using axios and then I am storing the responses from the axios in an array (for example responsePromises) and then I am executing
return Promise.all(responsePromises).then(responses => { {
//do something with the responses[0], responses[1], etc...
}
So the above is something that I already use successfully. The issue is, now I need to do the same in my back-end (which I mentioned is a NodeJS project).
Below is given the code that I am using for retrieving the data from a single endpoint and on success, I am executing the successMethod.
let paramArray = ["one", "two", "three"];
let parsedURL = url.parse("https://this-is-my-url.com?param=" + paramArray[0]);
let proto = reqUrl.indexOf('https://') === 0 ? https : http;
let request = proto.get({
hostname: parsedURL.hostname,
port: parsedURL.port,
path: parsedURL.path
}, (response) => {
//do something on success
successMethod();
})
request.on('error', (e) => {
//do something on error
errorMethod();
});
request.end();
What I am not sure is, how to apply the implementation with Promise.all inside of the NodeJS code which I am using? Would be ideal if I can somehow loop through the paramArray array again, to store the promises into another responsePromises array and to execute my successMethod only when I have all the responses returned.
You can await Promise all, that makes sure all promises have returned.
proto.get({
hostname: parsedURL.hostname,
port: parsedURL.port,
path: parsedURL.path
}, async(response) => {
//make responsePromises array
const res = await Promise.all(responsePromises);
//do something on success
successMethod();
})
EDIT after comment:
const responsePromises = paramArray.map(i=>{
let parsedURL = url.parse("https://this-is-my-url.com?param=" + i);
let proto = reqUrl.indexOf('https://') === 0 ? https : http;
const request = proto.get({
hostname: parsedURL.hostname,
port: parsedURL.port,
path: parsedURL.path
});
request.on('error', (e) => {
//do something on error
errorMethod();
});
return request;
})
Promise.all(responsePromises).then(responses => {
successMethod();
})
This was the solution to my problem:
let responsePromises = [];
paramArray.forEach(i=>{
const myPromise = new Promise((resolve, reject) => {
let parsedURL = url.parse("https://this-is-my-url.com?param=" + i);
let proto = reqUrl.indexOf('https://') === 0 ? https : http;
const request = proto.get({
hostname: parsedURL.hostname,
port: parsedURL.port,
path: parsedURL.path
}, (response) => {
resolve(response);
})
request.on('error', (e) => {
reject(e);
});
request.end();
});
responsePromises.push(myPromise);
})
Promise.all(responsePromises).then(responses => {
responses.forEach(response => {
successMethod();
});
})

Chaining async await calls in Node/Express with an external time limit

I'm building a Slackbot that makes a call to an Express app, which then needs to 1) fetch some other data from the Slack API, and 2) insert resulting data in my database. I think I have the flow right finally using async await, but the operation is timing out because the original call from the Slackbot needs to receive a response within some fixed time I can't control. It would be fine for my purposes to ping the bot with a response immediately, and then execute the rest of the logic asynchronously. But I'm wondering the best way to set this up.
My Express route looks like:
const express = require('express');
const router = express.Router();
const knex = require('../../db/knex.js');
const slack = require('../../services/slack_helpers');
// POST api/slack/foo
router.post('/foo', async (req, res) => {
let [body, images] = await slack.grab_context(req);
knex('texts')
.insert({ body: body,
image_ids: images })
.then(text => { res.send('worked!'); }) // This sends a response back to the original Slackbot call
.catch(err => { res.send(err); })
});
module.exports = router;
And then the slack_helpers module looks like:
const { WebClient } = require('#slack/web-api');
const Slack = new WebClient(process.env.SLACKBOT_TOKEN);
async function grab_context(req) {
try {
const context = await Slack.conversations.history({ // This is the part that takes too long
channel: req.body.channel_id,
latest: req.headers['X-Slack-Request-Timestamp'],
inclusive: true,
limit: 5
});
} catch (error) {
return [error.toString(), 'error'];
}
return await parse_context(context);
};
function parse_context(context) {
var body = [];
context.messages.forEach(message => {
body.push(message.text);
});
body = body.join(' \n');
return [body, ''];
}
module.exports = {
grab_context
};
I'm still getting my head around asynchronous programming, so I may be missing something obvious. I think basically something like res.send perhaps needs to come before the grab_context call? But again, not sure the best flow here.
Update
I've also tried this pattern in the API route, but still getting a timeout:
slack.grab_context(req).then((body, images) => {
knex ...
})
Your timeout may not be coming from where you think. From what I see, it is coming from grab_context. Consider the following simplified version of grab_context
async function grab_context_simple() {
try {
const context = { hello: 'world' }
} catch (error) {
return [error.toString(), 'error']
}
return context
}
grab_context_simple() /* => Promise {
<rejected> ReferenceError: context is not defined
...
} */
You are trying to return context outside of the try block where it was defined, so grab_context will reject with a ReferenceError. It's very likely that this error is being swallowed at the moment, so it would seem like it is timing out.
The fix is to move a single line in grab_context
async function grab_context(req) {
try {
const context = await Slack.conversations.history({
channel: req.body.channel_id,
latest: req.headers['X-Slack-Request-Timestamp'],
inclusive: true,
limit: 5
});
return await parse_context(context); // <- moved this
} catch (error) {
return [error.toString(), 'error'];
}
};
I'm wondering the best way to set this up.
You could add a higher level try/catch block to handle errors that arise from the /foo route. You could also improve readability by staying consistent between async/await and promise chains. Below is how you could use async/await with knex, as well as the aforementioned try/catch block
const express = require('express');
const router = express.Router();
const knex = require('../../db/knex.js');
const slack = require('../../services/slack_helpers');
const insertInto = table => payload => knex(table).insert(payload)
const onFooRequest = async (req, res) => {
try {
let [body, images] = await slack.grab_context(req);
const text = await insertInto('texts')({
body: body,
image_ids: images,
});
res.send('worked!');
} catch (err) {
res.send(err);
}
}
router.post('/foo', onFooRequest);
module.exports = router;

Lambda Axios post to second API weird async behaviour

I am having trouble with my lambda function. This is the current setup:
Lambda makes post request to API. API fetches data from postgres database and returns this data. When I use Postman or a local version of my lambda function, this works. When I use the actual lambda function, the API returns data with null.
Below are some code snippets:
lambda:
const axios = require('axios')
exports.handler = async (event) => {
let rows = await axios.post('http:/server/getData', {
params: {
var: event.var
}
})
if(rows.data.statusCode == 204){
//no data available
}
else{
data = rows.data.info
}
};
API Router Component
var router = express.Router()
const Pool = require('pg').Pool
const mgmtdb = new Pool({ ... })
router.post('/getData', function(req, res){
database.query("SELECT info FROM table WHERE var=$1", [req.body.var], (error, results) => {
const rows = results.rows
if (error) {
throw error
}
let status = rows.length == 0 ? 204 : 200
var responseJSON ={};
responseJSON.statusCode = status;
responseJSON.info= rows[0] ? rows[0].info : null;
res.json(responseJSON);
})
})
module.exports = router
When I call the API from Postman I get statusCode: 200 (data available).
If I call the API with the exact same data from lambda I get statusCode: 204 (no data available).
I believe that this is some async timing problem. I don't know how the responses from the API can differ ..
Is it possible that the API streams the response back to the originator for some time not just an impulse? And starts by streaming "no data available" and then, after a few milliseconds "oh, i found some data, here it is"? And that Postman waits for the stream to finish and the lambda function doesn't?
Thanks in advance!
Looks like your query is returning null because the req.body.var doesn't exist. Can you try changing your lambda function to this:
const axios = require('axios')
exports.handler = async (event) => {
let rows = await axios.post('http:/server/getData', {
var: event.var
})
if(rows.data.statusCode == 204){
//no data available
}
else{
data = rows.data.info
}
};
This basically removes the extra level params and makes the req.body.var work.
Sometimes lambda have issue with async await so i suggest you to write code like this
exports.handler = async (event, callback) => {
axios.post('http:/server/getData', {
var: event.var
})
.then((response) => {
callback(null, {
statusCode: 200,
body: JSON.stringify(response)
})
})
};

Resources