sorry for the generic title. I'm pretty new to nodejs as well as the idea of async/await.
So I have an express app, which makes an HTTP get request as a callback function. The callback function gets the body object, and returns it to getBody function. But when I try to assign getBody to a variable, it returns undefined.
Yes I know. The getBody function returns body before body gets filled up with data, but I just don't know how to write a getter method for this body object. So my question is, how can I run the get request and access body object in the global scope at the same time, so all functions depending on body object can run without further problems.
async function getBody (req, res, next) {
let body = await makeRequest(req, res);
return body; // THIS RETURNS UNDEFINED
}
router.get('/', getBody);
function makeRequest (req, res){
let uri;
let options = {
uri: uri,
};
request(options, function (error, response, body) {
if (error){
console.log('error:', error);
} else {
console.log('Success! statusCode:', response && response.statusCode);
let jsonObject = JSON.parse(body);
return jsonObject;
}
});
}
I did my research, but I just could not find a useful resource. Thanks in advance.
await and async should be used with a promise, these kind of method cannot return data. return is used to return a value from a synchronous method.
So you may return a promise from your makeRequest method like this,
async function getBody(req, res, next) {
let body = await makeRequest(req, res);
return body; // NOW BODY IS NOT UNDEFINED, call next() or send response here
}
router.get('/', getBody);
function makeRequest(req, res) {
return new Promise((resolve, reject) => {
let uri;
let options = {
uri: uri,
};
request(options, function (error, response, body) {
if (error) {
console.log('error:', error);
return reject(error);
} else {
console.log('Success! statusCode:', response && response.statusCode);
let jsonObject = JSON.parse(body);
return resolve(jsonObject);
}
});
})
}
FYI,
let body = await makeRequest(req, next)
is equals to
makeRequest(req, next).then(body => { /* YOUR CODE HERE */ })
and if you didn't knew, you have to process the body and send the response, return body won't send the response to the client.
OK, #JanithKasun did a great job of answering your original question fully. This answer is intended to expand on that a little to get a the problem you're having conceptually that isn't explicitly asked in your question.
As I understand your code, you're trying to fetch some data from a third party resource based on information in the request coming to your app's handler. Ideally, you want to separate your code in a way to make it more reusable/maintainable. I note that the code in question doesn't actually use the request or response object at all, but I'm going to assume you would have some kind of parameter to the getBody function that helps you construct the URI it's requesting. So, to that end:
// ./lib/get-body.js
const BASE_URI = 'https://example.com/search?q='
async function getBody (query) {
let body = await makeRequest(query);
return body;
}
function makeRequest(query) {
return new Promise((resolve, reject) => {
let uri = `${BASE_URI}{query}`; // results in something like 'https://example.com/search?q=cats'
let options = {
uri: uri,
};
// Note: removed console statements here to centralize error handling
request(options, function (error, response, body) {
if (error) {
return reject(error);
} else {
let jsonObject = JSON.parse(body);
return resolve(jsonObject);
}
});
})
}
// export the top level function for reuse
module.exports = getBody;
Now, in your routing code:
// ./index.js or wherever
const express = require('express');
const getBody = require('./lib/get-body');
//...whatever other setup...
app.get('/', async (req, res, next) => {
const query = req.query.terms; // or whatever
try {
const body = await getBody(query);
return res.send(body);
} catch (e) {
return next(e); // if you don't do this, process hangs forever on error
}
});
// handle the errors. obviously you can do something smart like
// figure out the error code and send back something other than a 500 if appropriate.
app.use((err, req, res, next) => {
console.error(err);
res.status(500).send('I am Bender; please insert girder.');
});
Hope that helps!
Related
I am trying to use node-http-proxy inside an AdonisJS controller, but I get the error
The "url" argument must be of type string. Received type function
The line causing the error is the proxy.web(request, response, { target: urlToProxy });
async proxy({ request, response }){
var resource = await Resource.query().where('uri', request.url()).with('service.user').with('userSecurity').first()
resource = resource.toJSON()
if(!resource.service.active){
return response.status(404).send(`Parent service '${resource.service.title}' is disabled`)
}
if(!resource.active){
return response.status(404).send(`Resource is disabled`)
}
if(resource.userSecurity.length <= 0) {
return response.status(403).send(`You are not permitted to access that resource. Contact ${resource.service.user.first_name} ${resource.service.user.last_name} (${resource.service.user.email})`)
}
var urlToProxy = url.resolve(resource.service.basepath, request.originalUrl())
var proxy = httpProxy.createProxyServer()
proxy.web(request, response, { target: urlToProxy });
}
In the end I got a bit closer but not a full fix. The getting close bit was to realise the http-proxy was delivering data via buffer so I had to do something like this
proxy.web(req, res, { target: data.service.basepath})
proxy.on('error', function(err, req, res){
console.log("There was an error", err)
})
proxy.on('proxyRes', async (proxyRes, request, response) =>{
var body = new Buffer('')
proxyRes.on('data', (data)=>{
body = Buffer.concat([body, data])
})
proxyRes.on('end', ()=>{
body = body.toString()
try{
res.send(body)
}catch(err){
}
})
});
However, I still could not get it to work as the controller was returning before http-proxy had completed the request.
In the end and probably for the best, I wrote a stand alone proxy app and used the main app just to validate JWT tokens before they go through the Proxy.
You were so close, I wanted to do something similar and wrapped the proxy in a promise so we can wait for the proxy to return before responding with our response:
const proxy = httpProxy.createProxyServer();
const prom = new Promise((resolve, reject) => {
proxy.web(request.request, response.response, {
target: urlToTarget
}, (e) => {
reject(e);
});
proxy.on('proxyRes', function (proxyRes, req, res) {
let body = [];
proxyRes.on('data', function (chunk) {
body.push(chunk);
});
proxyRes.on('end', function () {
body = Buffer.concat(body).toString();
resolve(body);
});
});
});
const result = await prom;
response.body(result);
return response;
I thought I'd give you a complete answer for anyone else that comes across this.
I'm working on an Actions on Google chatbot using Dialogflow and Cloud Functions. The runtime is Node.js 6.
Why does this function return the empty string?
function getJSON(url) {
var json = "";
var request = https.get(url, function(response) {
var body = "";
json = 'response';
response.on("data", function(chunk) {
body += chunk;
json = 'chunk';
});
response.on("end", function() {
if (response.statusCode == 200) {
try {
json = 'JSON.parse(body)';
return json;
} catch (error) {
json = 'Error1';
return json;
}
} else {
json = 'Error2';
return json;
}
});
});
return json;
}
This is the intent in which I want to access the json data:
app.intent('test', (conv) => {
conv.user.storage.test = 'no change';
const rp = require("request-promise-native");
var options = {
uri: 'https://teamtreehouse.com/joshtimonen.json',
headers: {
'User-Agent': 'Request-Promise'
},
json: true // Automatically parses the JSON string in the response
};
rp(options)
.then(function (user) {
conv.user.storage.test = user.name;
})
.catch(function (err) {
conv.user.storage.test = 'fail';
});
conv.ask(conv.user.storage.test);
});
You can try to use the request module for node.js, I tried my self reproducing your use case and worked fine. The code should be something similar to this:
const request = require('request');
request(url, {json: true}, (err, res, body) => {
if (err) { res.send(JSON.stringify({ 'fulfillmentText': "Some error"})); }
console.log(body);
});
Also, you need to add "request": "2.81.0" in the dependencies section inside your package.json file.
The function returns the empty string because https sets up a callback function, but the program flow continues to the return statement before the callback is called.
In general, when working with Dialogflow Intent Handlers, you should be returning a Promise instead of using callbacks or events. Look into using request-promise-native instead.
Two clarifying points:
You must return the Promise. Otherwise Dialogflow will assume the Handler has completed. If you return the Promise, it will wait for the Promise to finish.
Everything you want to send back must be done inside the then() block. This includes setting any response. The then() block runs after the asynchronous operation (the web API call) completes. So this will have the results of the call, and you can return these results in your call to conv.ask().
So it might look something like this:
return rp(options)
.then(function (user) {
conv.add('your name is '+user.name);
})
.catch(function (err) {
conv.add('something went wrong '+err);
});
You can use Axios as well
To install Axios : npm install axios
OR
You can add it in package.json as a dependency
"dependencies": {
"axios": "^0.27.2",
}
index.js
const axios = require('axios').default;
exports.makeRequest = async (req, res) => {
axios.get('https://jsonplaceholder.typicode.com/todos/1')// Dummy URL
.then(function (response) {
// handle success
res.status(200).json({
success:true,
result:response.data
})
})
.catch(function (error) {
// handle error
console.log(error);
})
};
OR
const Axios = require("axios");
exports.makeRequest = async (req, res) => {
const { data } = await
Axios.get('https://jsonplaceholder.typicode.com/todos/1')
res.status(200).send({data})
};
Right now i have this code
router.get('/export', function(req, res, next) {
var postData, eventData, messageData, userData
Posts.list().then(data=> {
var jsonOutput=JSON.stringify(data)
postData=jsonOutput //this doesnt work
})
.catch(erro => res.status(500).send('error'))
Events.list().then(data=> {
var jsonOutput=JSON.stringify(data)
eventData=jsonOutput //this doesnt work
})
.catch(erro => res.status(500).send('error'))
Messages.list().then(data=> {
var jsonOutput=JSON.stringify(data)
messageData=jsonOutput //this doesnt work
})
.catch(erro => res.status(500).send('error'))
Users.list().then(data=> {
var jsonOutput=JSON.stringify(data)
userData=jsonOutput //this doesnt work
})
.catch(erro => res.status(500).send('error'))
//Then when all data from colections is retrieve i want to use the 4 variables that i created in the beggining
});
So basicly im trying to retrieve the data from my mongo database and then assign the results to that 4 variables that i create, but im not getting success.
For what i´ve been seeing i have to use async but im having some trouble doing it.
I don't like too much mrlanlee solution. This is a typical situation where using async / await can really make sense. Anyway, the Hugo's solution (the second one, with async await), even if it just works, will make the four queries in sequence, one after another to. If you want a clean, working and parallel solution, check this:
router.get('/export', async function(req, res, next) {
let data
try {
data = await Promise.all([
Posts.list(),
Events.list(),
Messages.list(),
Users.list()
]);
// at this point, data is an array. data[0] = Posts.list result, data[1] = Events.list result etc..
res.status(200).json(data)
} catch (e) {
res.status(500).send('error');
}
});
The other answer from Sashi is on the right track but you will probably run into errors. Since your catch statement on each promise returns 500, if multiple errors are caught during the query, Express will not send an error or 500 each time, instead it will throw an error trying to.
See below.
router.get('/export', function(req, res, next) {
var postData, eventData, messageData, userData
try {
postData = Posts.list().then(data=> {
return JSON.stringify(data);
});
eventData = Events.list().then(data=> {
return JSON.stringify(data)
});
messageData = Messages.list().then(data=> {
return JSON.stringify(data);
})
userData = Users.list().then(data=> {
return JSON.stringify(data)
});
} catch (err) {
// this should catch your errors on all 4 promises above
return res.status(500).send('error')
}
// this part is optional, i wasn't sure if you were planning
// on returning all the data back in an object
const response = {
postData,
eventData,
messageData,
userData,
};
return res.status(200).send({ response })
});
For explanation of why you weren't able to mutate the variables, see Sashi's answer as he explains it.
The variables defined outside the async code is out of scope of the async functions. Hence you cannot store the returned value from the async functions in those variables.
This should work.
router.get('/export', function(req, res, next) {
var postData, eventData, messageData, userData
postData = Posts.list().then(data=> {
var jsonOutput=JSON.stringify(data);
return jsonOutput;
}).catch(erro => res.status(500).send('error'));
eventData = Events.list().then(data=> {
var jsonOutput=JSON.stringify(data);
return jsonOutput;
}).catch(erro => res.status(500).send('error'));
messageData = Messages.list().then(data=> {
var jsonOutput=JSON.stringify(data);
return jsonOutput;
}).catch(erro => res.status(500).send('error'));
userData = Users.list().then(data=> {
var jsonOutput=JSON.stringify(data);
return jsonOutput;
}).catch(erro => res.status(500).send('error'));
});
Using Async/Await is a much neater solution.
router.get('/export', async function(req, res, next) {
var postData, eventData, messageData, userData;
try{
postData = await Posts.list();
eventData = await Events.list();
messageData = await Messages.list()
userData = await Users.list();
catch (e){
res.status(500).send('error');
}
});
here is the code of index.js file
var request = require('request');
var requestHandling = require('../routes/request_handling_functions');
router.get("/example1", function (req, res) {
var result =
requestHandling.requestMethodGet('http://localhost:8083/getUserInfo/865c2c25-
d9e7-412d-a064-326bd66c9e9c', res);
console.log("===RESULT=====");
console.log(result);
});
in above code I want that function requestMethodGet return the result into result variable then I manipulate the result according to my need then show to user and also I am console my return result.
but here is the problem with it because Node.js is asynchronous language so first it print the result then it call the function requestMethodGet that is so irritating.
Here is the code of requestMethodGet
requestMethodGet: function (url, res) {
//SET ALL THESE PARATMETER TO MAKE REQUEST
request.get({url: url}, function (e, r, body) {
var errorResult = module.exports.validateResponseeData(e);
console.log("====errorResult===in===Get==method====");
console.log(errorResult);
if (errorResult != "continue") {
console.log("===im in not continue");
return errorResult;
} else {
//LOGING THE RESPONSE BODY
log.info('body:', body);
var responseData = JSON.parse(body);
console.log("======RESPONSE=========DATA=====================");
console.log('error:', e);
console.log('statusCode:', r && r.statusCode);
console.log('body:', body);
console.log("====================================");
console.log(responseData);
return responseData;
}
});
}
I want that the router get method run the code in the sequence as the code write. but I search it everywhere I not found the any solution so come here to find my solution.
if any information is needed to solve this question then please inform me.
Try the following. It returns a promise from your requestMethodGet function and you then wait until that is resolved before logging the response. I suggest you read more on Promises to understand how this works.
requestMethodGet: function (url, res) {
return new Promise((resolve,reject) => {
//SET ALL THESE PARATMETER TO MAKE REQUEST
request.get({url: url}, function (e, r, body) {
var errorResult = module.exports.validateResponseeData(e);
console.log("====errorResult===in===Get==method====");
console.log(errorResult);
if (errorResult != "continue") {
console.log("===im in not continue");
reject(errorResult);
} else {
//LOGING THE RESPONSE BODY
log.info('body:', body);
var responseData = JSON.parse(body);
console.log("======RESPONSE=========DATA=====================");
console.log('error:', e);
console.log('statusCode:', r && r.statusCode);
console.log('body:', body);
console.log("====================================");
console.log(responseData);
resolve(responseData);
}
});
});
}
//INDEX.js
router.get("/example1", function (req, res) {
requestHandling.requestMethodGet('http://localhost:8083/getUserInfo/865c2c25-
d9e7-412d-a064-326bd66c9e9c', res).then((result) => {
console.log("===RESULT=====");
console.log(result);
}).catch((err) => {
console.log("===Error=====");
console.log(err);
});
});
you can use Promise or async/await
I am trying to use a curried fn with a closure as a callback to the servers request event.
It works the first time a client makes a request (the html is sent and diplayed) but the second time the page is blank. Does node erase the closure after calling the cb once?
// server.js
var pype = function(self, arr, errorHandler, cb) {
var i = 0,
req,
res,
run = function() {
arr[i].call(self, req, res, next);
},
next = function(err) {
if (err && errorHandler) {
return errorHandler.call(self, err, req, res);
}
i++
if (i < arr.length) {
return run();
} else if (cb) {
return cb.call(self, req, res);
}
}
return function(request, response) {
req = request;
res = response;
run();
}
};
var http = require("http”);
var server = http.createServer();
var errorhandler = function(err, req, res) {
res.end(err);
},
var finalHandler = function(req, res) {
res.setHeader("Content-Type", "text/html");
res.end(req.html);
};
var requestHandler = pype(null, [pens, gits, concat, index, sort, html, finalHandler], errorhandler);
// this works only once
server.on('request', requestHandler);
// this works for multiple calls
server.on('request', function(req, res){
var requestHandler = pype(null, [pens, gits, concat, index, sort, html, finalHandler], errorhandler);
requestHandler(req, res);
});
Thanks!
Node doesn't erase any closure.
I looks to me like you never reset i back to 0 when you are done with a request or starting a new request. Because the closure retains all its state indefinitely, when the 2nd request comes in, i will point beyond the end of the array and you will never call any of the functions in the array.
You can reset iback to 0 either when you're doing with a request or at the beginning of a new request.
Change from this:
return function(request, response) {
req = request;
res = response;
run();
}
to this:
return function(request, response) {
req = request;
res = response;
i = 0;
run();
}
to fix that specific issue, but you have other issues too (see below).
It appears this structure has other problems though. Because you're creating only a single closure and then reusing it for all requests, whenever your first request goes async to process something and a second request arrives during that time, you will wipe out the closure variables of the first request and the two will collide. This scheme of a single permanent closure is an untenable design for any server ever meant to handle more than one request at a time.
Your 2nd attempt that works just fine is a much better way to do things because you create a new independent closure for each request which will not have the race condition issue when multiple requests are in flight at the same time.
I must say, I really don't think the curried style is needed here at all. You don't want to create a lasting closure that persists indefinitely (you need a new one for each request). And, it makes your code a lot harder to understand and follow.
It seems much simpler to me without the currying:
var http = require("http");
var server = http.createServer();
function handleRequest(ctx, req, res, handlers) {
return new Promise(function(resolve, reject) {
var i = 0;
function next(err) {
if (err) {
reject({err, req, res, ctx});
} else {
run();
}
}
function run() {
if (i < handlers.length) {
handlers[i++].call(ctx, req, res, next);
} else {
resolve({req, res, ctx});
}
}
run();
}
}
function errorhandler(info) {
info.res.end(info.err);
}
function finalHandler(info) {
info.res.setHeader("Content-Type", "text/html");
info.res.end(req.html);
}
server.on('request', function(req, res) {
handleRequest(null, req, res, [pens, gits, concat, index, sort, html]).then(finalHandler, errorHandler);
});