This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 4 years ago.
I am writing a class in JavaScript that sends HTTP requests for a specific URL. I'm trying to test that class with Mocha but for some reason, the method fetchUrl() returns undefined. I can't seem to figure out why. I literally started writing in JavaScript a day ago, therefore I am still trying to learn and adjust to it.
fetchUrl () {
var request = require('request')
var res
request(this.url, function (error, response, body) {
console.log('error:', error) // Print the error if one occurred
if (response.statusCode !== 200) {
console.log('received status code other than 200 OK')
this.error = true
}
res = response
console.log('statusCode:', response && response.statusCode) // Print the response status code if a response was received
// console.log('body:', body) // Print the HTML for the requested url.
this.html = body
})
return res
}
describe('Test Http request to google.com', function () {
it('should return 200', function (done) {
assert.equal(httpCon.fetchUrl().statusCode, 200)
done()
})
})
You should use Nock libray to mocking HTTP request.
const axios = require('axios');
module.exports = {
getUser(username) {
return axios
.get(`https://api.github.com/users/${username}`)
.then(res => res.data)
.catch(error => console.log(error));
}
};
And here test case:
describe('Get User tests', () => {
beforeEach(() => {
nock('https://api.github.com')
.get('/users/octocat')
.reply(200, response);
});
});
For more details, you can look at this: mocking-http also look into this answer of SO. source
I think you should only return res inside the callback, otherwise it will return undefined since the program keeps running...
https://developer.mozilla.org/en-US/docs/Glossary/Callback_function
You may want to take a look on callback funcionality
Related
This question already has answers here:
nodejs - How to promisify http.request? reject got called two times
(6 answers)
Closed 1 year ago.
const http = require("http");
async function sendRequest(url) {
url = new URL(url);
const requestDetails = {
'hostname': url.hostname,
'port': url.port || 80,
'path': url.pathname,
'method': 'GET'
};
const req = await new Promise((resolve, reject) => {
const request = http.request(requestDetails, response => {
const status = response.statusCode;
if (status === 200 || status === 201) {
console.log("SUCCESS");
resolve(request);
} else {
console.log("ERROR");
reject(`Status code returned was ${status}`);
}
});
});
req.end();
}
sendRequest('http://httpbin.org/get');
It works when req.end() is inside the promise, but after passing the request out then execute req.end(), the console is just holding without any response. I tried to compare "req === request" by a middle variable, it returned true. Why doesn't moving end() out work? Shouldn't these two object the same?
The purpose of the req.end() is to finish the request. We might be cautious that if any body part is unsent or might in progress, it will flush them in the stream, or if any request is chunked, this will send to terminating.
I have implemented your same code in a bit different and cleaner way. Below way might help to reuse the same code for multiple apis.
const http = require("http");
/**
* #description call the http request
*/
async function doHttp(requestDetails){
return new Promise((resolve, reject)=>{
http.request(requestDetails, response => {
const status = response.statusCode;
response.setEncoding("utf-8");
if (status === 200 || status === 201) {
console.log("SUCCESS");
response.on('data', data => {
return resolve(data);
});
} else {
console.error("ERROR");
return reject(new Error("emptyData"));
}
}).on('error', (err) => {
// Catch the error if occured in request
console.error(`problem with request: ${e.message}`);
return reject(err);
}).end();
});
}
/**
* #description sending the request
*/
async function doSend(url) {
url = new URL(url);
const requestDetails = {
'hostname': url.hostname,
'port': url.port || 80,
'path': url.pathname,
'method': 'GET'
};
const data = await doHttp(requestDetails)
console.log(data);
}
doSend('http://httpbin.org/get');
At last, we could say req.end() is required to finish up any request. It completely depends on us, how we can implement a method.
An alternate solution might be this native https module is such as Axios, superagent, got, node-fetch. They provide a wrapper over the native nodejs code which might help us to control to handle an error and response.
You should move the request.end call inside the promise otherwise it just newer gets called because you will be waiting for a promise that is newer resolved because the request is not send.
Also you should reject the promise in case request object emits error event.
INTRODUCTION
I am implementing a function for making any kind of https request to any endpoint (using the https native module). When I make a request to a specific API I get an error response in JSON format. Like this:
{
"error": {
"code": 404,
"message": "ID not found"
}
}
How can I handle this kind of errors? At a first moment, I supposed that they were handled in
request.on("error", (err) => {
reject(err);
});
HTTPs Request function code
I have comment '<---------' in the relevant parts of the code
const https = require("https");
exports.httpsRequest = function (options, body = null) {
/*
This function is useful for making requests over the HTTPs protocol
*/
return new Promise((resolve, reject) => {
const request = https.request(options, (response) => {
// Get the response content type
const contentType =
response.headers["content-type"] &&
response.headers["content-type"].split(";")[0];
// Cumulate data
let chuncks = [];
response.on("data", (chunck) => {
chuncks.push(chunck);
});
response.on("end", () => {
// Concat all received chunks
let response = Buffer.concat(chuncks);
// Some responses might be in JSON format...
if (contentType === "application/json") {
// Jsonify the response
response = JSON.parse(response);
}
// (For the future) TODO - Check and parse more content types if needed.
// Resolve the promise with the HTTPs response
resolve(response); // <--------- The JSON format error responses are resolved too!!
});
});
// Reject on request error
request.on("error", (err) => {
// <------------- At a first moment, I supposed that all error responses were handled in this part of the code
reject(err);
});
// Write the body
if (body) {
request.write(body);
}
// Close HTTPs connection.
request.end();
});
};
Question
Why the error response is not handled in request.on("error", ...) ?
Thank you. I would appreciate any help or suggestion.
You need to create a different code path for when the content type isn't what you were expecting in which you call reject() and you also need to try/catch around JSON parsing errors so you can properly catch them and reject on them too. You can solve those issues with this code:
exports.httpsRequest = function (options, body = null) {
/*
This function is useful for making requests over the HTTPs protocol
*/
return new Promise((resolve, reject) => {
const request = https.request(options, (response) => {
// Get the response content type
const contentType =
response.headers["content-type"] &&
response.headers["content-type"].split(";")[0];
// Cumulate data
let chuncks = [];
response.on("data", (chunck) => {
chuncks.push(chunck);
});
response.on("end", () => {
// Concat all received chunks
let response = Buffer.concat(chuncks);
// Some responses might be in JSON format...
if (contentType === "application/json") {
try {
// Jsonify the response
response = JSON.parse(response);
resolve(response);
return;
} catch(e) {
reject(e);
return;
}
}
reject(new Error("Not JSON content-type"))
});
});
// Reject on request error
request.on("error", (err) => {
reject(err);
});
// Write the body
if (body) {
request.write(body);
}
// Close HTTPs connection.
request.end();
});
};
FYI, libraries such as got() and others listed here, all do this work for you automatically and have a lot of other useful features. You don't really need to build this yourself.
Hello I'm trying to test this API call but I don't know how to test for the status code of the response since it is a real (and it has to stay like that) API call and not a mock one
this is the function I'm testing:
export const getDataFromApi = (url) => {
return axios.get(url)
.then(({ data }) => data)
.catch(err => console.log(err.toString()));
}
and this is the test:
describe('Read data from API', () => {
test('Get result of the API call', (done) => {
const apiUrl = "https://rickandmortyapi.com/api/character";
getDataFromApi(apiUrl)
.then(data => {
expect(data).toBeDefined();
expect(data.results.length).toBeGreaterThan(0);
done();
});
});
});
how can I expect if the status code of data is 200 or if is another status code?
also is necessary for me to leave that done after the execution of the function? I know with call backs I have to put it but with this promise I'm not sure
Axios has a single response object returned in both the success and error paths which contains the HTTP status code. An error is raised if the response is not in the 2xx range.
You can plumb the status code as a return object from your getDataFromApi() wrapper function, but you'll probably want the full response object for other checks (like headers). I recommend getting rid of the wrapper altogether.
Without the wrapper, here's 2 different status checks using promises, one for success and one for failure:
describe('Read data from API', () => {
test('Get successful result of the API call', async() => {
const apiUrl = "https://rickandmortyapi.com/api/character";
await axios.get(apiUrl)
.then(r => {
expect(r.data).toBeDefined();
expect(r.data.results.length).toBeGreaterThan(0);
expect(r.status).toBeGreaterThanOrEqual(200);
expect(r.status).toBeLessThan(300);
})
.catch(e => {
fail(`Expected successful response`);
});
});
test('Get failure result of the API call', async() => {
const apiUrl = "https://rickandmortyapi.com/api/character-bad";
await axios.get(apiUrl)
.then(r => {
fail(`Expected failure response`);
})
.catch(e => {
if (e.response) {
expect(e.response.status).toBeGreaterThanOrEqual(400);
expect(e.response.status).toBeLessThan(500);
} else {
throw e;
}
});
});
});
I am trying to issue an HTTP request to another web service, from a Google Cloud Function (GCF) that I have created. I need the HTTP request to complete and return that result inside of my GCF so that I can do something else with it.
My question is; What is the best way to use Promise inside a Google Cloud Function? Is what I am trying to do possible?
My code currently looks like this:
export const MyGCF = functions.https.onRequest((request, response) => {
let dayOfTheWeek: any;
const request1 = require('request');
const url = 'http://worldclockapi.com/api/json/pst/now';
function getDay() {
return new Promise((resolve, reject) => {
request1(url, { json: true }, (err: any, res: any, body: any) => {
if (err) {
return reject(err);
}
resolve(body.dayOfTheWeek);
});
});
}
getDay().then((data) => {
dayOfTheWeek = data;
console.log(dayOfTheWeek);
});
});
In general your approach will work, and you can define additional functions inside of your MyGCF handler, in the same way that you have defined getDay(). One problem with you current code however is that you're forgetting to "write a response" for the request being processed by MyGCF.
You can write a response for the request by calling send() on the second res argument of your MyGCF request handler. A simple example would be:
/* Sends a response of "hello" for the request */
res.send("hello");
With respect to your code, you can use res.send() in your .then() callback to send a response back to the client after getDay() has completed (see code below). Note also to include a .catch() clause and callback for the error case (with an error status) to ensure the client receives an appropriate error response if the call to getDay() fails:
export const MyGCF = functions.https.onRequest((req, res) => {
const request = require('request');
const url = 'http://worldclockapi.com/api/json/pst/now';
function getDay() {
return new Promise((resolve, reject) => {
request(url, {
json: true
}, (err: any, r: any, body: any) => {
if (err) {
reject(err);
} else {
resolve(body.dayOfTheWeek);
}
});
});
}
getDay().then((dayOfTheWeek) => {
/* Send a response once the getDay() request complete */
res.send(dayOfTheWeek);
})
.catch(err => {
/* Don't forget the error case */
res.status(500).send(err);
});
});
I am trying to write a simple GET request that returns the JSON data at https://hacker-news.firebaseio.com/v0/item/160705.json
I have tried a number of things and nothing seems to work. (I'm on the paid Firebase plan that allows requests to external API's). I write the function, then run firebase deploy and execute the function but it either times out or throws another error.
As a test, this simple HTTP call works fine:
exports.helloWorld = functions.https.onRequest((request, response) => {
response.send('test');
})
But when I try to run the following, hitting the HN API, it times out:
exports.helloWorld = functions.https.onRequest((request, response) => {
request.get('https://hacker-news.firebaseio.com/v0/item/160705.json', function (error, res, body) {
if (!error && res.statusCode == 200) {
console.log(body) // Print the google web page.
}
return response.send("") // this terminates the function
})
})
EDIT
The firebase log for the above function says:
Function execution started
Function execution took 60002 ms, finished with status: 'timeout'
I've also tried a couple other things, for example:
const options = {
host: 'hacker-news.firebaseio.com',
path: '/v0/item/160705.json'
};
// make the request
exports.hackerNews = functions.https.onRequest(options, (resp) => {
console.log(resp)
});
but that fails with a 500 Error: could not handle the request and Referrer Policy: no-referrer-when-downgrade
It shouldn't be this difficult to write a simple GET request in firebase functions, so I must be doing something dumb. Thanks.
I figured it out:
exports.helloWorld = functions.https.onRequest((req, res) => {
request.get('https://hacker-news.firebaseio.com/v0/item/160705', (error, response, body) => {
if (!error && response.statusCode === 200) {
return res.send(body);
}
return res.send('ERROR: ' + error.message);
})
});
Apparently you have to return something on success or error, you just can't execute another function like console.log().