I want to make a backend call to an external api's and populate my page with the results. What is the best way to do this?
The "request.get" call is asynchronous, so I understand the code below is erroneous. However, I have written it in that fashion so that I can explain what I want to actually do.
Further, I may have 5-6 external api, is there a way to make this asynchronous for every api but synchronous get call?
This is how my current code looks like:
var express = require('express');
var request = require('request');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
var body = getRawApiResponse("someURL");
console.log("Index >" + body);
res.render('index', { title: 'Express', api: "some", body: body});
});
function getRawApiResponse(api){
request.get({
uri: api,
},
function(error, response, body){
if (!error && response.statusCode === 200) {
console.log("Index > Raw Api Response: " + body);
} else {
console.log(error);
}
});
}
You can wrap getRawApiResponse() in a promise
function getRawApiResponse(api){
return new Promise(function(resolve, reject){
request.get({
uri: api,
},
function(error, response, body){
if (!error && response.statusCode === 200) {
resolve(body)
} else {
reject(error)
}
});
});
}
which resolves on success and rejects in case of an error then you can chain it inside the get request like
router.get('/', function(req, res, next) {
getRawApiResponse("someURL")
.then(function(body){
res.render('index', { title: 'Express', api: "some", body: body});
})
.catch(err){
// do something
}
});
Look into Promises or async/await. You can use them to call your async apis and wait for the response making the call synchronous.
http://bluebirdjs.com/docs/getting-started.html
A Sample code of async/ await which u can modify for ur purpose is as below:
try{
let orderDetails = await MongoHelper.findOneByCriteria(MongoCollections.ORDER,searchCriteria);
}catch(err){
return err;
}
MongoHelper.findOneByCriteria = (collectionName, criteria) => {
return new Promise((resolve, reject) => {
db.collection(collectionName).find(criteria).toArray()
.then((results) => {
if (results.length > 0) {
resolve(results[0]);
} else {
resolve(null);
}
});
});
}
The best way is to use Promises to avoid callbacks hell. If you can use node.js v7.6 or higher, it could be much easier with async/await.
router.get('/', function(req, res, next) {
getRawApiResponse("someURL")
.then(body => {
console.log("Index >" + body);
res.render('index', { title: 'Express', api: "some", body: body});
});
});
function getRawApiResponse(uri) {
return new Promise((resolve, reject) => {
request.get({ uri }, (error, response, body) => {
if (err) {
reject(err);
}
resolve(body);
});
});
}
In the example about I use promisification to return a promise from getRawApiResponse, but there is already a module which do the same https://github.com/request/request-promise.
Related
In my application i have to perform a series of API calls step by step.
I tried to achieve this using the async waterfall option .But before getting the response of the first API, second function is getting executed and same thing is happening in the second function also. That is before getting the response, final result is send .
If i try to perform some task other than API calls , the waterfall operation is happening properly.
Below is the code i have tried. For testing purpose same API is called from both functions (myFirstFunction, mySecondFunction).
const async = require('async');
router.get('/', (req, res) => {
async.waterfall([
myFirstFunction,
mySecondFunction,
],
function (err, result) {
if (err) {
console.log("Error-->" + JSON.stringify(err));
res.status(400).json(err).end();
} else {
console.log(" Result -->" + JSON.stringify(result));
res.status(200).json("Success").end();
}
});
});
const myFirstFunction = (callback) => {
console.log(" ------- myFirstFunction ");
const vehList = callVehicle();
console.log("First Function -->" + JSON.stringify(vehList));
callback(null, vehList);
}
const mySecondFunction = (vehList, callback) => {
console.log("-------- mySecondFunction");
const vehList1 = callVehicle();
const vehList2 = {
"1": vehList,
"2": vehList1
}
console.log("Second Function -->" + JSON.stringify(vehList2));
callback(null, vehList2);
}
const callVehicle = () => {
var options = {
method: "GET",
json: true,
strictSSL: false,
url: `http://localhost:8080/vehicle/make`
};
request(options, function(error, response, body) {
if (body){
console.log("Success REST Response: ", JSON.stringify(body));
return body;
} else {
console.log("Error : ", JSON.stringify(error));
return {"Error": "error"};
}
});
}
Output obtained
F:\workSpace_Node\SampleApp>node app.js
server running at 9086
------- myFirstFunction
First Function -->undefined
-------- mySecondFunction
Second Function -->{}
Result -->{}
Success REST Response: {"vehicleList":[{"make":"Audi","model":"A3","vin":"QVFCFQT7894563214"},{"make":"Audi","model":"A4","vin":"ASECFQT7894563214"},{"make":"Audi","model":"Q7"},{"make":"Audi","model":"Q5","vin":"QWECFQT7894993214"}]}
Success REST Response: {"vehicleList":[{"make":"Audi","model":"A3","vin":"QVFCFQT7894563214"},{"make":"Audi","model":"A4","vin":"ASECFQT7894563214"},{"make":"Audi","model":"Q7"},{"make":"Audi","model":"Q5","vin":"QWECFQT7894993214"}]}
How to achieve this using async.waterfall or is there any better approach for this requirement.
The best way for me to use Promises and asynchronous functions.
But if you want to do it with without promises, I think that all of your code that is asynchronous should get a callback parameter.
But your callVehicle has not callback parameter, so parent function cannot be notified when call Vehicle took a response.
const myFirstFunction = (callback) => {
callVehicle(callback);
}
const mySecondFunction = (vehList, callback) => {
const vehList1 = callVehicle((err, res) => callback (err, {
1: vehList,
2: res
}));
}
// We add callback that should be called when we have a result of the api request
const callVehicle = (callback) => {
var options = {
method: "GET",
json: true,
strictSSL: false,
url: `http://localhost:8080/vehicle/make`
};
request(options, function(error, response, body) {
if (!error && body){
console.log("Success REST Response: ", JSON.stringify(body));
callback(null, body)
} else {
console.log("Error : ", JSON.stringify(error));
callback({ Error: error }, null)
});
}
With promises:
const get = (options) => new Promise(
(resolve, reject) => request(
{method: 'GET', ...options},
(err, response, body)=> err ? reject(err) : resolve(body)
)
)
const callVehicle = () => get({
json: true,
strictSSL: false,
url: `http://localhost:8080/vehicle/make`
})
router.get('/', async (req, res) => {
try {
const firstVehicle = await callVehicle()
const secondVehicle = await callVehicle()
res.status(200).json("Success").end();
} (error) {
res.status(400).json(error).end();
}
});
I'm trying to create REST API. My API should return a list of users taken from a 3rd party (after some manipulations) and return it.
Here is my code:
function getUsersFrom3rdParty(options) {
https.get(options, (resp) => {
let data ='';
// A chunk of data has been received.
resp.on('data', (chunk) => {
data += chunk;
});
// The whole response has been received. Print out the result.
resp.on('end', () => {
console.log(JSON.parse(data));
});
}).on("error", (err) => {
console.log("Error: " + err.message);
});
}
exports.getUsers = (req, res, next) => {
var data = getUsersFrom3rdParty();
//do the manilupations and return to the api
};
I don't get the data in getUsers function.
I'd suggest using something like axios - npmjs - for making asynchronous calls to a 3rd party API:
const axios = require('axios')
function getUsersFrom3rdParty(options) {
const processResponse = (response) => {
const processedResponse = ...
// do whatever you need to do, then return
return processedResponse
}
return axios.get('/example.com')
.then(processResponse)
}
// then, you can use `getUsersFrom3rdParty` as a promise
exports.getUsers = (req, res, next) => {
const handleResponse = (data) => {
res.json({ data }) // or whatever you need to do
}
const handleError = (err) => {
res.json({ error: 'Something went wrong!' }) // or whatever you need to do
}
getUsersFrom3rdParty(...)
.then(handleResponse)
.catch(handleError)
}
This way, you're waiting for your API call to finish before you render something and/or return a response.
You are not passing options variable when you are calling getUsersFrom3rdParty function
var data = getUsersFrom3rdParty(options);
You have to pass options to make it work and I suggest to use request module .It works better than https module.
Here is your code using request
const request = require("request");
function getUsersFrom3rdParty(options) {
request(options, (error, response, body) => {
if (!error && response.statusCode == 200) {
//Returned data
console.log(JSON.parse(body));
}
});
}
exports.getUsers = (req, res, next) => {
var data = getUsersFrom3rdParty(options);
};
i'm beginner at nodejs, i got a problem when request multiple url in a loop then i render it.
Error: Can't set headers after they are sent.
at validateHeader (_http_outgoing.js:491:11)
at ServerResponse.setHeader (_http_outgoing.js:498:)
router.get('/', function(req, res, next) {
setInterval(function(){
request(url1,function (error,response,body) {
var data1 = JSON.parse(body);
request(url2+data1.access_token,function (error,response,body) {
var data_info = JSON.parse(body);
//error when it render
res.render('index', {data_info : data_info});
})
})
},5000);
});
That's not exactly a loop, I understand you mean that you call the same function repeteadly with setInterval().
Once you've sent your first response with res.render(), which finishes the response process for that request, subsequent attempts to use that res object fail.
If you want to send data to the client in 5 seconds interval you should probably either look into websockets or pass the setInterval() calls to the client so it polls your server each 5 seconds, in which case your server code could be changed to:
router.get('/', (req, res) => {
request(url1, (error, response, body) => {
const data1 = JSON.parse(body);
request(`${url2}${data1.access_token}`, (error, response, body) => {
const data_info = JSON.parse(body);
res.render('index', { data_info });
});
});
});
You can make use of Async Module
const async = require('async');
router.get('/', function (req, res, next) {
async.waterfall([
function(callback) {
request(url1, function (error,response,body) {
if(err) {
callback(err)
}else {
var data1 = JSON.parse(body);
callback(data1)
}
})
},
function(data1, callback) {
request(url2+data1.access_token, function(error,response,body) {
if(err) {
callback(err)
}else {
var data_info = JSON.parse(body);
callback(null, data_info)
}
})
}
], function(err, result) {
if(err) {
res.json({success: false, error: err, message: "Something went wrong.!"})
}else {
res.render('index', {
data_info : result
});
}
})
})
I am writing a route that checks if a system app is online and then responds to the client with a simple 200 status ok, or a 404 status.
I'm using express and request to make the api call.
Route looks like this:
app.get('/status/keymgr', async (req, res, next) => {
try{
var endpoint = `http://${config.KeyManager.host}:${config.KeyManager.adminPort}/healthcheck`;
console.log(endpoint);
await request.get(endpoint, function(err, response, body){
if (!err && response.statusCode == 200){
res.send('OK');
}else{
res.status(404);
}
}).end();
}catch(error){
res.status(404);
}finally{
next();
}
});
For some reason, I am getting the following error:
uncaughtException: Can't set headers after they are sent.
I am guessing some kind of response is being sent to the browser before the route runs the res.send() or the res.status().
I can't figure out what's wrong here. Any idea??
AS #ndugger mentioned, the reason you are getting this exception is because request.get does not return a promise and hence await here is of no use. You have two options, either you use util.promisify or wrap your request under a new promise and resolve only when the callback finishes. Something like this
app.get('/status/keymgr', async (req, res, next) => {
var endpoint = `http://${config.KeyManager.host}:${config.KeyManager.adminPort}/healthcheck`;
console.log(endpoint);
try {
await new Promise((resolve, reject) => {
request.get(endpoint, function (err, response, body) {
if (!err && response.statusCode == 200) {
// res.send('OK');
resolve('OK');
} else {
reject('404')
// res.status(404);
}
});
});
res.send('OK');
} catch (err) {
res.status(404);
} finally {
next();
}
}
For a certain route, I have the following code:
router.get('/:id', function(req, res) {
var db = req.db;
var matches = db.get('matches');
var id = req.params.id;
matches.find({id: id}, function(err, obj){
if(!err) {
if(obj.length === 0) {
var games = Q.fcall(GetGames()).then(function(g) {
console.log("async back");
res.send(g);
}
, function(error) {
res.send(error);
});
}
...
});
The function GetGames is defined as follows:
function GetGames() {
var url= "my-url";
request(url, function(error, response, body) {
if(!error) {
console.log("Returned with code "+ response.statusCode);
return new Q(body);
}
});
}
I'm using the request module to send a HTTP GET request to my URL with appropriate parameter, etc.
When I load /:id, I see "Returned with code 200" logged, but "async back" is not logged. I'm also not sure that the response is being sent.
Once GetGames returns something, I want to be able to use that returned object in the route for /:id. Where am I going wrong?
Since GetGames is an async function write it in node.js callback pattern:
function GetGames(callback) {
var url= "my-url";
request(url, function(error, response, body) {
if(!error) {
console.log("Returned with code "+ response.statusCode);
return callback(null,body)
}
return callback(error,body)
});
}
Then use Q.nfcall to call the above function and get back a promise:
Q.nfcall(GetGames).then(function(g) {
})
.catch()