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})
};
Related
I found the aproach how to put the async function into a router endpoint and tried to implement it but not suceed. E.g. this link zellwk.com
The server sends only empty string instead of a huge string that is on snippet.host.
async function downloadData(url) {
const options = {
'method': 'GET', 'url': url, 'headers': {}, formData: {}
};
let result = '';
await request(options, function (error, response) {
if (error) throw new Error(error);
console.log(response.body)
result = response.body
});
return {error: 0, text: result}
}
app.get('/token/:token', jsonParser, async function (req, res) {
console.log(req.params.token) //Don't mind this token
try {
let message = await downloadData('https://snippet.host/xgsh/raw')
res.send(message)
} catch (e) {
console.log(e);
res.sendStatus(500);
}
});
How to make it wait for the download?
EDIT
I implemented node-fetch and got this:
{"error":0,"text":{"size":0}}
What did I wrong?
async function downloadData(url) {
const result = await fetch(url)
return {error: 0, text: result}
}
app.get('/token/:token', jsonParser, async function (req, res) {
console.log(req.params.token)
try {
let message = await downloadData('https://snippet.host/xgsh/raw')
res.send(message)
} catch (e) {
console.log(e);
res.sendStatus(500);
}
});
in case of fetch call the response body need to be parsed first which you are missing.
async function downloadData(url) {
const res = await fetch(url)
if (res && res.status != 200)
throw new Error("status code other than 200 received")
let json = await res.json()
return {error: 0, text: json}
}
app.get('/token/:token', async function (req, res) {
console.log(req.params.token)
try {
let message = await downloadData('https://snippet.host/xgsh/raw')
// let json = await message.json()
res.send(message)
} catch (e) {
console.log(e);
res.sendStatus(500);
}
});
i tried calling the api but every time it is returning 404 . so json cant be called in that case. but if 200 is returned then you call json and return that accordingly.
You can only usefully await a promise.
request doesn't return a promise, it takes a callback function instead.
This is one of the reasons that request was deprecated two years ago.
Replace request with something which supports promises such as node-fetch, the native fetch that was added to Node.js recently, or axios.
In case you must use request (eventhough it's deprecated); or in case you find a similar situation with a old function that doesn't return a promise; you can turn your request into a function that returns a promise.
Either write your own wrapper, in the form of
function requestPromise(options) {
return new Promise(
(resolve,reject) => request(options,
(err,response) => if (err) reject(err) else resolve(response)));
}
or, given that request's callback is in the form of function(err,response), directly use util.promisify
async function xxxx(...) {
try {
...
let response = await util.promisify(request)(options);
...
}
Because request isn't actually an async function that returns a Promise, you can create your own async version of request.
function asyncRequest(options) {
return new Promise((resolve, reject) => {
request(options, (err, response) => {
if (err) {
reject(err)
} else {
resolve(response)
}
})
})
}
Then in your project rewrite your downloadData function to be:
async function downloadData(url) {
const options = {
method: "GET",
url: url,
headers: {},
formData: {},
}
const result = await asyncRequest(options)
return { error: 0, text: result.body }
}
I started by creating a return statement in the request function (I have linked a picture) and then console.log it outside of the function but that didn't work out.
My server code
var options = {
'method': 'POST',
'url': 'http://localhost:8080/ES_Part1/api/user/getUser',
'headers': {
'Content-Type': 'application/x-www-form-urlencoded'
},
form: {
'username': username,
'password': password
}
};
requestToApi(options, function(error, response) {
if (error) throw new Error(error);
console.log("Send form data to remote api and to return the user from Spring")
console.log(response.body);
return response.body
});
var fromapi = response.body;
res.end();
Example:
I suggest you use a Promise-based approach here rather than the callback-style that you're using for requestToApi. If you're using the request package, there is a Promise-based version available.
Alternative solution would be to create a promise yourself, like such:
var requestToApiAsPromise = (options) => {
return new Promise((resolve, reject) => {
requestToApi(options, (error, response) => {
if (error) {
reject(error)
return
}
resolve(response.body)
})
})
}
Then you can use this method in your middleware:
app.post("/checkUser", (req, res) => {
async function process() {
try {
var username = req.body.username
var password = req.body.password
var options = {...}
var response = await requestToApiAsPromise(options)
// response => response.body
// do whatever
res.end()
} catch (error) {
next(error)
}
}
process()
})
This method uses async/await so that it lets you write your code as if you were doing things synchronously, so it's making it easier to make asynchronous calls and have them "wait" before the next line gets executed.
👨🏫 If you want to get respose.body outside the handler, than you can use this code below 👇:
// an example get function
app.get('/users', async(req, res) => {
var options = {
'method': 'POST',
'url': 'http://localhost:8080/ES_Part1/api/user/getUser',
'headers': {
'Content-Type': 'application/x-www-form-urlencoded'
},
form: {
'username': username,
'password': password
}
};
const result = new Promise((resolve, reject) => {
requestToApi(options, function(error, response) {
if (error) return reject(error);
return resolve(JSON.parse(response.body));
});
})
// make sure, to use async in your function
// because we're using await here
var fromapi = await result;
// It's working here
console.log(fromapi);
res.end();
})
That code above 👆, only an example that you can use to read response.body. If you want to handle the error from that code above, you can use like this code below:
try {
// make sure, to use async in your function
// because we're using await here
var fromapi = await result;
// It's working here
console.log(fromapi);
} catch(ex) {
// print error response
console.log(ex.message);
}
I hope it's can help you 🙏.
I'm trying to grab text from an API that only returns a string of text ((here)) and having troubles throwing that out in a response. When posting, it comes out as [object Response], and the console.log doesn't show the text I want out of it.
The code I'm using:
fetch('http://taskinoz.com/gdq/api').then(
function(response) {
console.log(response);
throttledSendMessage(channel, response);
return response;
})
.catch(function(error) {
throttledSendMessage(channel, "An error has occured");
})
Log can be found here
Thanks for looking with me, couldn't find a solution :/
I think that because fetch returns a Response you need to call one of the functions on Response in order to get at the body's text. Here's an example:
fetch('https://github.com/')
.then(res => res.text())
.then(body => console.log(body));
Probably the problem is in async behavior of node.js. You can read more here
Also, I'm assume you use this package to make fetch request in node.js.
And assume that throttledSendMessage function is synchronous.
About your problem, just try to rewrite co de to use async/await for cleaner solution.
// We'll use IIFE function
(async () => {
const fetchResult = await fetch('http://taskinoz.com/gdq/api')
// Here fetch return the promise, so we need to await it again and parse according to our needs. So, the result code would be this
const data = await fetchResult.text();
throttledSendMessage(channel, data);
return data;
})()
Fetch is not available in nodejs , you could use the node-fetch https://www.npmjs.com/package/node-fetch , or you could use this fetch function:
const https = require('https');
const http = require('http');
function fetch(url, options = {}) {
return new Promise((resolve, reject) => {
if (!url) return reject(new Error('Url is required'));
const { body, method = 'GET', ...restOptions } = options;
const client = url.startsWith('https') ? https : http;
const request = client.request(url, { method, ...restOptions }, (res) => {
let chunks = '';
res.setEncoding('utf8');
res.on('data', (chunk) => {
chunks += chunk;
});
res.on('end', () => {
resolve({ statusCode: res.statusCode, body: chunks });
});
});
request.on('error', (err) => {
reject(err);
});
if (body) {
request.setHeader('Content-Length', body.length);
request.write(body);
}
request.end();
});
}
module.exports = fetch;
you could use it like this inside your code :
const result = await fetch(
`https://YOUR_URL`,
{
method: 'PUT',
body: JSON.stringify({ test: 'This is just a test', prio: 1 }),
},
);
I'm writing unit tests for separate middleware functions in Node/Express using Jest.
A simple example of the middleware:
function sendSomeStuff(req, res, next) {
try {
const data = {'some-prop':'some-value'};
res.json(data);
next();
} catch (err) {
next(err);
}
}
And a sample of my test suite:
const httpMocks = require('node-mocks-http');
const { sendSomeStuff } = require('/some/path/to/middleware');
describe('sendSomeStuff', () => {
test('should send some stuff', () => {
const request = httpMocks.createRequest({
method: 'GET',
url: '/some/url'
});
let response = httpMocks.createResponse();
sendSomeStuff(request, response, (err) => {
expect(err).toBeFalsy();
// How to 'capture' what is sent as JSON in the function?
});
});
});
I have to provide a callback to populate the next parameter, which is called in the function. Normally, this would 'find the next matching pattern', and pass the req and res objects to that middleware. However, how can I do this in a test set-up? I need to verify the JSON from the response.
I don't want to touch the middleware itself, it should be contained in the test environment.
Am I missing something here?
Found a fix!
Leaving this here for someone else who might struggle with the same.
When returning data using res.send(), res.json() or something similar, the response object (from const response = httpMocks.createResponse();)
itself is updated. The data can be collected using res._getData():
const httpMocks = require('node-mocks-http');
const { sendSomeStuff } = require('/some/path/to/middleware');
describe('sendSomeStuff', () => {
test('should send some stuff', () => {
const request = httpMocks.createRequest({
method: 'GET',
url: '/some/url'
});
const response = httpMocks.createResponse();
sendSomeStuff(request, response, (err) => {
expect(err).toBeFalsy();
});
const { property } = JSON.parse(response._getData());
expect(property).toBe('someValue');
});
});
});
I did a different way by utilising jest.fn(). For example:
if you wanna test res.json({ status: YOUR_RETURNED_STATUS }).status(200);
const res = {};
res.json = jest.fn(resObj => ({
status: jest.fn(status => ({ res: { ...resObj, statusCode: status }
})),
}));
Basically, I mock the res chain methods(json and status).
That way you can do expect(YOUR_TEST_FUNCTION_CALL).toEqual({ res: { status: 'successful', statusCode: 200 }}); if your response structure is like that.
The application is making API request to the server and returning back the response received. Here would like to understand
(i) Is it right way to make API call and receive response from the server.
(ii) if return statement passing back the value is right way
The module is called as
str = apiRequest(search, lang);
And the module is
var https = require('https');
function apiRequest(search, lang) {
var options = {
host: 'localhost:8080',
path: '/api?search=' + search + '&lang=' + lang
};
function resData(res) {
console.log("Status Code : ", res.statusCode);
var str = '';
res.on('data', function (chunk) {
str += chunk;
});
res.on('end', function () {
return JSON.parse(str); // Is this a right way to return data
});
res.on('error', function(e) {
console.error(e);
});
}
https.get(options, resData).end();
};
No, this will not work. I will comment the relevant parts below.
Instead of having this in apiRequest:
function apiRequest(search, lang) {
function resData(res) {
// ...
res.on('end', function () {
return JSON.parse(str); // Is this a right way to return data
});
// ...
}
https.get(options, resData).end();
}
and calling it with:
str = apiRequest(search, lang);
You should either pass a callback or return a promise.
Using callbacks
Your apiRequest function can take an additional argument, a callback:
function apiRequest(search, lang, callback) {
function resData(res) {
// ...
res.on('end', function () {
callback(null, JSON.parse(str));
});
res.on('error', function(e) {
console.error(e);
callback(e);
});
// ...
}
https.get(options, resData).end();
}
Now you can use it as:
apiRequest(search, lang, function (error, str) {
if (err) {
// you have error
} else {
// you have your str here
}
});
Using promises
Your apiRequest function can return a promise:
function apiRequest(search, lang, callback) {
return new Promise(function (resolve, reject) {
function resData(res) {
// ...
res.on('end', function () {
resolve(JSON.parse(str));
});
res.on('error', function(e) {
console.error(e);
reject(e);
});
// ...
}
https.get(options, resData).end();
}
}
Now you can use it as:
apiRequest(search, lang)
.then(function (str) {
// you have your str here
})
.catch(function (err) {
// you have error
});
This is not tested so there might be some minor mistakes but that is the general idea. When I find some errors I'll update the answer.
Summary
To sum it up, there are two styles that you can use to compose asynchronous functions like that: callbacks or promises. You will not be able to just return the data because return is fundamentally synchronous - you have to have something to return right away - unless what you return is a promise that can get resolved or rejected later.
Parsing JSON
What should also keep in mind that you should always run JSON.parse() inside a try {} catch {} block to handle errors of incorrect JSON or otherwise the entire app could crash. JSON.parse() throws exceptions on bad input. See this answer for more info.
This will additionally complicate your code but you can avoid that complication and make it even simpler by using the request module - see updates below.
Simpler examples
Callbacks
To have a working example that is simpler and your don't have to manually parse JSON, consider this code that I just wrote, based on your example but calling a GitHub API so it can be tested by everyone - it prints someone's website given his GitHub nickname but otherwise works similarly to your code:
'use strict';
var request = require('request');
function apiRequest(search, callback) {
var options = {
url: 'https://api.github.com/users/' + search,
json: true,
headers: {'User-Agent': 'request'}
};
function resData(err, res, data) {
if (err) {
callback(err);
} else if (res.statusCode !== 200) {
callback(res.statusCode);
} else {
// data is already parsed as JSON:
callback(null, data.blog);
}
}
request.get(options, resData);
}
apiRequest('rsp', function (err, data) {
if (err) {
console.log('Error:', err);
} else {
console.log('Data:', data);
}
});
This is an example using callbacks.
Promises
And here is an example using promises:
'use strict';
var request = require('request');
function apiRequest(search, callback) {
return new Promise(function (resolve, reject) {
var options = {
url: 'https://api.github.com/users/' + search,
json: true,
headers: {'User-Agent': 'request'}
};
function resData(err, res, data) {
if (err) {
reject(err);
} else if (res.statusCode !== 200) {
reject(res.statusCode);
} else {
// data is already parsed as JSON:
resolve(data.blog);
}
}
request.get(options, resData);
});
}
apiRequest('rsp')
.then(function (data) {
console.log('Data:', data);
})
.catch(function (err) {
console.log('Error:', err);
});
Simplified
It can be simplified even further by using fat arrow functions and anonymous functions and object literals:
'use strict';
var request = require('request');
function apiRequest(search, callback) {
return new Promise((resolve, reject) => {
request.get({
url: 'https://api.github.com/users/' + search,
json: true,
headers: {'User-Agent': 'request'}
}, (err, res, data) => {
if (err) {
reject(err);
} else if (res.statusCode !== 200) {
reject(res.statusCode);
} else {
resolve(data.blog);
}
});
});
}
apiRequest('rsp')
.then(data => console.log('Data:', data))
.catch(err => console.log('Error:', err));
More info
You can see some other answers where I explain the difference between callbacks and promises and how to use the together in more detail, which you may find helpful:
A detailed explanation on how to use callbacks and promises
Explanation on how to use promises in complex request handlers
An explanation of what a promise really is, on the example of AJAX requests
An explanation on how to handle errors in callbacks and promises