I am trying to periodically load some data from external API like this:
setInterval(function() {
getData();
}, 60000);
function getData() {
if (typeof someObject.data === 'object') {
for (var prop in someObject.data) {
if (prop === 1 || prop === 2) {
var options = {
host: 'somehost.com',
path: '/somepath?param=' + prop
};
var req = http.request(options, function(res) {
// EXECUTION NEVER REACHES THIS POINT ?!?!
req.on('end', function() { alert('ended'); });
});
req.end();
}
}
}
}
If I do not do any intervals and loops, such request to the same host works perfectly. However, if I try to do something like shown above, then the request never ever calls its callback function.
What I am doing wrong here?
I think one of your conditions are bad, the following works fine for me.
var http = require('http');
setInterval(function() {
getData();
}, 1000);
function getData() {
console.log('get');
//if (typeof someObject.data === 'object') {
console.log('get 1');
//for (var prop in someObject.data) {
console.log('get 2');
//if (prop === 1 || prop === 2) {
console.log('get 3');
var options = {
host: 'google.com',
path: '/'
};
var req = http.request(options, function(res) {
console.log('http request', res.statusCode);
//req.on('end', function() {
// console.log('ended', req);
//});
});
req.end();
//}
//}
//}
}
also If I'm right, you don't need req.on('end'), the callback of the request is called when it's completed. You can also use http.get, so you don't need to call req.end
var req = http.get( options.host, function(res) {
console.log('http request', res.statusCode);
//req.on('end', function() {
// console.log('ended', req);
//});
}).on('error', function( e ) {
console.error( 'error', e );
})
see more info in the docs
hope I could help.
Related
Well, i'm visiting an array of urls making a request for each one, when one request ends the method executes the next. The array is something like this: [link1,link2,link3]. If i try to open first the link3 in browser i'll get an error (error 404) but opening the link1 and link2 first i'll have the desired response. In the browser works without problems, but isn't working in my code because i got "status:200" using the first two links, but a 404 with the third.
(If i open link2 and link2 in the browser the problem ends, but i want to do that not using the browser)
The code:
function websiteOpener(links_array, index, final) {
var methodStr = className + '::websiteOpener';
try {
log.info(methodStr + '>> Open the link: ' + links_array[index]);
var protocol;
var _host;
var rawhost;
if (links_array[index].match(/https:\/\/[^\/]+/)) {
rawhost = links_array[index].match(/https:\/\/[^\/]+/);
_host = rawhost[0].replace(/https:\/\//, '');
protocol = 'https:'
_path = links_array[index].replace(rawhost, '');
incremental = index + 1;
var options = {
host: _host,
path: _path,
method: 'GET',
headers: { 'Content-type': 'text/html' },
protocol: protocol,
agent: new https.Agent({
rejectUnauthorized: false,
})
}
} else {
incremental = index + 1;
var options =links_array[index];
}
if (incremental < final) {
if (links_array[index].match(/https:\/\/[^\/]+/)) {
var request = https.request(options, function (response) {
console.log(response.statusCode);
//if (response.statusCode === 200) {
var data;
response.on('data', (chunk) => {
data += chunk;
});
response.on('end', function () {
websiteOpener(links_array, incremental, final);
});
//}
});
request.end();
} else {
var request = http.request(options, function (response) {
//if (response.statusCode === 200) {
var data;
response.on('data', (chunk) => {
data += chunk;
});
response.on('end', function () {
websiteOpener(links_array, incremental, final);
});
//}
});
request.end();
}
} else {
options.headers = { 'Content-type': 'applcation/pdf' };
var request = https.request(options, function (response) {
console.log(response.statusCode);
//if (response.statusCode === 200) {
var data;
response.on('data', (chunk) => {
data += chunk;
});
response.on('end', function () {
log.info(methodStr + '>>link found ' + links_array[index]);
});
//}
});
request.end();
}
} catch (e) {
log.error(methodStr + ">> Server error: ", e);
reject({ statusCode: 500, flag: 'ERR_PROCESS' });
}
}
I want to call an api from a Lambda function. My handler is triggered by an intent which includes two required slots. Therefore I don't know in advance whether I will be returning a Dialog.Delegate directive or my response from the api request. How do I promise these return values by the time the intent handler is called?
This is my handler:
const FlightDelayIntentHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest'
&& handlerInput.requestEnvelope.request.intent.name === 'MyIntent';
},
handle(handlerInput) {
const request = handlerInput.requestEnvelope.request;
if (request.dialogState != "COMPLETED"){
return handlerInput.responseBuilder
.addDelegateDirective(request.intent)
.getResponse();
} else {
// Make asynchronous api call, wait for the response and return.
var query = 'someTestStringFromASlot';
httpGet(query, (result) => {
return handlerInput.responseBuilder
.speak('I found something' + result)
.reprompt('test')
.withSimpleCard('Hello World', 'test')
.getResponse();
});
}
},
};
This is my helper function which makes the request:
const https = require('https');
function httpGet(query, callback) {
var options = {
host: 'myHost',
path: 'someTestPath/' + query,
method: 'GET',
headers: {
'theId': 'myId'
}
};
var req = https.request(options, res => {
res.setEncoding('utf8');
var responseString = "";
//accept incoming data asynchronously
res.on('data', chunk => {
responseString = responseString + chunk;
});
//return the data when streaming is complete
res.on('end', () => {
console.log('==> Answering: ');
callback(responseString);
});
});
req.end();
}
So I suspect I will have to use promises and put an "async" in front of my handle function? I am very new to all of this so I don't know the implications of this, especially considering the fact that I have two different return values, one directly and the other one delayed. How would I solve this?
Thank you in advance.
As you suspected, your handler code is finishing before the asynchronous call to http.request, hence the Alexa SDK receives no return value from the handle function and will return an invalid response to Alexa.
I slightly modified your code to run it locally on a laptop to illustrate the issue :
const https = require('https');
function httpGet(query, callback) {
var options = {
host: 'httpbin.org',
path: 'anything/' + query,
method: 'GET',
headers: {
'theId': 'myId'
}
};
var req = https.request(options, res => {
res.setEncoding('utf8');
var responseString = "";
//accept incoming data asynchronously
res.on('data', chunk => {
responseString = responseString + chunk;
});
//return the data when streaming is complete
res.on('end', () => {
console.log('==> Answering: ');
callback(responseString);
});
});
req.end();
}
function FlightDelayIntentHandler() {
// canHandle(handlerInput) {
// return handlerInput.requestEnvelope.request.type === 'IntentRequest'
// && handlerInput.requestEnvelope.request.intent.name === 'MyIntent';
// },
// handle(handlerInput) {
// const request = handlerInput.requestEnvelope.request;
// if (request.dialogState != "COMPLETED"){
// return handlerInput.responseBuilder
// .addDelegateDirective(request.intent)
// .getResponse();
// } else {
// Make asynchronous api call, wait for the response and return.
var query = 'someTestStringFromASlot';
httpGet(query, (result) => {
console.log("I found something " + result);
// return handlerInput.responseBuilder
// .speak('I found something' + result)
// .reprompt('test')
// .withSimpleCard('Hello World', 'test')
// .getResponse();
});
console.log("end of function reached before httpGet will return");
// }
// }
}
FlightDelayIntentHandler();
To run this code, do not forget npm install http , then node test.js. It produces
stormacq:~/Desktop/temp $ node test.js
end of function reached before httpGet will return
==> Answering:
I found something {
"args": {},
"data": "",
...
So, the key is to wait for http get to return before to return a response to Alexa. For that, I propose to modify your httpGet function to return a promise instead using callbacks.
Modified code is like this (I kept your original code as comment)
const https = require('https');
async function httpGet(query) {
return new Promise( (resolve, reject) => {
var options = {
host: 'httpbin.org',
path: 'anything/' + query,
method: 'GET',
headers: {
'theId': 'myId'
}
};
var req = https.request(options, res => {
res.setEncoding('utf8');
var responseString = "";
//accept incoming data asynchronously
res.on('data', chunk => {
responseString = responseString + chunk;
});
//return the data when streaming is complete
res.on('end', () => {
console.log('==> Answering: ');
resolve(responseString);
});
//should handle errors as well and call reject()!
});
req.end();
});
}
async function FlightDelayIntentHandler() {
// canHandle(handlerInput) {
// return handlerInput.requestEnvelope.request.type === 'IntentRequest'
// && handlerInput.requestEnvelope.request.intent.name === 'MyIntent';
// },
// handle(handlerInput) {
// const request = handlerInput.requestEnvelope.request;
// if (request.dialogState != "COMPLETED"){
// return handlerInput.responseBuilder
// .addDelegateDirective(request.intent)
// .getResponse();
// } else {
// Make asynchronous api call, wait for the response and return.
var query = 'someTestStringFromASlot';
var result = await httpGet(query);
console.log("I found something " + result);
// return handlerInput.responseBuilder
// .speak('I found something' + result)
// .reprompt('test')
// .withSimpleCard('Hello World', 'test')
// .getResponse();
//});
console.log("end of function reached AFTER httpGet will return");
// }
// }
}
FlightDelayIntentHandler();
Running this code produces :
stormacq:~/Desktop/temp $ node test.js
==> Answering:
I found something{
"args": {},
"data": "",
...
end of function reached AFTER httpGet will return
I use NodeJS and request lib to make some request to an API.
I understand now that all requests are async and so it doesn't "wait" the result of the GET call, so the index of my Loop is always the same.
I was wondering if there was any simple way (without any lib) to wait for the response of the request call ?
For now, my code is this :
for (var i in entries) {
var entryId = entries[i]['id'];
var options = {
url: 'https://api.com/'+ entryId +'/get/status',
method: 'GET',
headers: {
'Authorization': auth
}
};
console.log(' ENTRY ID > '+ entryId);
request(options, function(error, response, body) {
var response = JSON.parse(body);
if (response.status.code == 200) {
var id = response.status.id;
var data = [];
data['id'] = id;
data = JSON.stringify(data);
// show first entryId of loop
console.log(' > MY ID : '+ id + ' - '+ entryId);
options = {
host: hostname,
port: 80,
path: '/path/function2',
method: 'PUT'
};
var post = http.request(options, function(json) {
var body = '';
json.on('data', function(d) {
body += d;
});
json.on('end', function() {
console.log('> DONE');
});
}).on('error', function(e) {
console.log(e);
});
post.write(data);
post.end();
}
});
}
You are looking for async/await.
Wrap your logic inside an async function, then you can await for the promise to resolve.
const request = require('request-promise')
async function foo (a) {
for (i in a)
try {
let a = await request('localhost:8080/')
// a contains your response data.
} catch (e) {
console.error(e)
}
}
foo([/*data*/])
Just use the promisified version of request module.
You also can use Promises to wait for your async code to finish.
function asyncCode(msg, cb){
setTimeout(function() {cb(msg);}, 1000);
}
var p1 = new Promises(function(resolve){
asyncCode("my asyncCode is running", resolve);
});
p1.then(function(msg) {
console.log(msg);
}).then(function() {
console.log("Hey I'm next");
});
console.log("SyncCode, Async code are waiting until I'm finished");
I'm trying to wrap http.request into Promise:
new Promise(function(resolve, reject) {
var req = http.request({
host: '127.0.0.1',
port: 4000,
method: 'GET',
path: '/api/v1/service'
}, function(res) {
if (res.statusCode < 200 || res.statusCode >= 300) {
// First reject
reject(new Error('statusCode=' + res.statusCode));
return;
}
var body = [];
res.on('data', function(chunk) {
body.push(chunk);
});
res.on('end', function() {
try {
body = JSON.parse(Buffer.concat(body).toString());
} catch(e) {
reject(e);
return;
}
resolve(body);
});
});
req.on('error', function(err) {
// Second reject
reject(err);
});
req.write('test');
}).then(function(data) {
console.log(data);
}).catch(function(err) {
console.log(err);
});
If I recieve errornous statusCode from remote server it will call First reject and after a bit of time Second reject. How to make properly so it calls only single reject (I think First reject is proper one in this case)? I think I need to close res myself, but there is no close() method on ClientResponse object.
UPD:
Second reject triggers very rarely - why?
Your code is almost fine. To restate a little, you want a function that wraps http.request with this form:
function httpRequest(params, postData) {
return new Promise(function(resolve, reject) {
var req = http.request(params, function(res) {
// on bad status, reject
// on response data, cumulate it
// on end, parse and resolve
});
// on request error, reject
// if there's post data, write it to the request
// important: end the request req.end()
});
}
Notice the addition of params and postData so this can be used as a general purpose request. And notice the last line req.end() -- which must always be called -- was missing from the OP code.
Applying those couple changes to the OP code...
function httpRequest(params, postData) {
return new Promise(function(resolve, reject) {
var req = http.request(params, function(res) {
// reject on bad status
if (res.statusCode < 200 || res.statusCode >= 300) {
return reject(new Error('statusCode=' + res.statusCode));
}
// cumulate data
var body = [];
res.on('data', function(chunk) {
body.push(chunk);
});
// resolve on end
res.on('end', function() {
try {
body = JSON.parse(Buffer.concat(body).toString());
} catch(e) {
reject(e);
}
resolve(body);
});
});
// reject on request error
req.on('error', function(err) {
// This is not a "Second reject", just a different sort of failure
reject(err);
});
if (postData) {
req.write(postData);
}
// IMPORTANT
req.end();
});
}
This is untested, but it should work fine...
var params = {
host: '127.0.0.1',
port: 4000,
method: 'GET',
path: '/api/v1/service'
};
// this is a get, so there's no post data
httpRequest(params).then(function(body) {
console.log(body);
});
And these promises can be chained, too...
httpRequest(params).then(function(body) {
console.log(body);
return httpRequest(otherParams);
}).then(function(body) {
console.log(body);
// and so on
});
I know this question is old but the answer actually inspired me to write a modern version of a lightweight promisified HTTP client. Here is a new version that:
Use up to date JavaScript syntax
Validate input
Support multiple methods
Is easy to extend for HTTPS support
Will let the client decide on how to deal with response codes
Will also let the client decide on how to deal with non-JSON bodies
Code below:
function httpRequest(method, url, body = null) {
if (!['get', 'post', 'head'].includes(method)) {
throw new Error(`Invalid method: ${method}`);
}
let urlObject;
try {
urlObject = new URL(url);
} catch (error) {
throw new Error(`Invalid url ${url}`);
}
if (body && method !== 'post') {
throw new Error(`Invalid use of the body parameter while using the ${method.toUpperCase()} method.`);
}
let options = {
method: method.toUpperCase(),
hostname: urlObject.hostname,
port: urlObject.port,
path: urlObject.pathname
};
if (body) {
options.headers = {'Content-Length':Buffer.byteLength(body)};
}
return new Promise((resolve, reject) => {
const clientRequest = http.request(options, incomingMessage => {
// Response object.
let response = {
statusCode: incomingMessage.statusCode,
headers: incomingMessage.headers,
body: []
};
// Collect response body data.
incomingMessage.on('data', chunk => {
response.body.push(chunk);
});
// Resolve on end.
incomingMessage.on('end', () => {
if (response.body.length) {
response.body = response.body.join();
try {
response.body = JSON.parse(response.body);
} catch (error) {
// Silently fail if response is not JSON.
}
}
resolve(response);
});
});
// Reject on request error.
clientRequest.on('error', error => {
reject(error);
});
// Write request body if present.
if (body) {
clientRequest.write(body);
}
// Close HTTP connection.
clientRequest.end();
});
}
There are other ways as well but here you can find a simple way to make http.request as a promise or async/await type.
Here is a working sample code:
var http = require('http');
function requestAsync(name) {
return new Promise((resolve, reject) => {
var post_options = {
host: 'restcountries.eu',
port: '80',
path: `/rest/v2/name/${name}`,
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
};
let post_req = http.request(post_options, (res) => {
res.setEncoding('utf8');
res.on('data', (chunk) => {
resolve(chunk);
});
res.on("error", (err) => {
reject(err);
});
});
post_req.write('test');
post_req.end();
});
}
//Calling request function
//:1- as promise
requestAsync("india").then(countryDetails => {
console.log(countryDetails);
}).catch((err) => {
console.log(err);
});
//:2- as await
let countryDetails = await requestAsync("india");
After reading all of these and a few articles, I thought I'd post a sort of "general" solution that handles both http and https:
const http = require("http");
const https = require("https");
const url_obj = require("url");
const request = async (url_string, method = "GET", postData = null) => {
const url = url_obj.parse(url_string);
const lib = url.protocol=="https:" ? https : http;
const params = {
method:method,
host:url.host,
port: url.port || url.protocol=="https:" ? 443 : 80,
path: url.path || "/"
};
return new Promise((resolve, reject) => {
const req = lib.request(params, res => {
if (res.statusCode < 200 || res.statusCode >= 300) {
return reject(new Error(`Status Code: ${res.statusCode}`));
}
const data = [];
res.on("data", chunk => {
data.push(chunk);
});
res.on("end", () => resolve(Buffer.concat(data).toString()));
});
req.on("error", reject);
if (postData) {
req.write(postData);
}
req.end();
});
}
You could use like this:
request("google.com").then(res => console.log(res)).catch(err => console.log(err))
This is heavily inspired by this article, but replaces the hacky url parsing with the built in api.
Hope this help.
const request = require('request');
async function getRequest() {
const options = {
url: 'http://example.com',
headers: {
'Authorization': 'Bearer xxx'
}
};
return new Promise((resolve, reject) => {
return request(options, (error, response, body) => {
if (!error && response.statusCode == 200) {
const json = JSON.parse(body);
return resolve(json);
} else {
return reject(error);
}
});
})
}
It's easier for you to use bluebird api, you can promisify request module and use the request function async as a promise itself, or you have the option of using the module request-promise, that makes you to not working to creating a promise but using and object that already encapsulates the module using promise, here's an example:
var rp = require('request-promise');
rp({host: '127.0.0.1',
port: 4000,
method: 'GET',
path: '/api/v1/service'})
.then(function (parsedBody) {
// GET succeeded...
})
.catch(function (err) {
// GET failed...
});
I've set up a NodeJS server which can be accessed by a client. Every once in a while it's necessary to let the server connect to a second server and feed the information retrieved back to the client.
Connecting to the second server is the easy part, but to be honest I have no idea how to send it back to the client. res.write seems to be forbidden during the connection with the second server.
The connection from the client is handled by handleGetRequest. The connection with the second server starts at http.get.
var http = require('http');
var url = require('url');
var server = http.createServer(function(req, res) {
var url_parsed = url.parse(req.url, true);
if (req.method ==='GET') {
handleGetRequest(res, url_parsed);
} else {
res.end('Method not supported');
}
});
handleGetRequest = function(res, url_parsed) {
if (url_parsed.path == '/secondary') {
var OPTIONS = {
hostname: "localhost",
port: "8900",
path: "/from_primary"
}
http.get(OPTIONS, function(secget) {
resget.on('data', function(chunk) {
// either store 'chunk' for later use or send directly
});
}).on('error', function(e) {
console.log("Error " + e.message);
});
} else {
res.writeHead(404);
}
res.end('Closed');
};
server.listen(8000);
How do I send the chunk from http.request to the client?
I thinks passing the callback to the handleGetRequest will fix this issue:
if (req.method === 'GET') {
handleGetRequest(url_parsed, function (err, response) {
if (err) {
return res.sendStatus(500);
}
res.json(response);
});
} else {
res.end('Method not supported');
}
handleGetRequest = function (url_parsed, callback) {
// OPTIONS ...
http.get(OPTIONS, function(resget) {
var data = '';
resget.on('data', function(chunk) {
data += chunk;
});
resget.on('end', function() {
callback(null, data);
});
}).on('error', function(e) {
callback(e);
});
}
Thanks to #TalgatMedetbekov for the suggestions. I managed to implement it like this:
var http = require('http');
var url = require('url');
var server = http.createServer(function(req, res) {
var url_parsed = url.parse(req.url, true);
if (req.method ==='GET') {
handleGetRequest(res, url_parsed);
} else {
res.end('Method not supported');
}
});
handleGetSecondaryRequest = function(callback, res) {
var OPTIONS = {
hostname: "localhost",
port: "8900",
path: "/from_primary"
}
var data = null;
http.get(OPTIONS, function(func, data) {
func.on('data', function(chunk) {
data += chunk;
});
func.on('end', function() {
callback(res, data);
});
}).on('error', function(e) {
callback(res, e);
})
};
var secReqCallback = function(res, recData)
{
res.write(recData);
res.end("END");
};
handleGetRequest = function(res, url_parsed) {
if (url_parsed.path == '/secondary') {
handleGetSecondaryRequest(secReqCallback, res);
} else {
res.writeHead(404);
}
};
server.listen(8000);
It works, kind of. There's an 'undefined' in front of the string which I can't find the cause for, but the basic functionality works perfect.
The callback construction is necessary to synchronize the asynchronous nature of NodeJS.