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);
});
});
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.
I have a function that makes an API request and receives data in json format.
async function getDataAboutWeather(url) {
return await new Promise(resolve => {
request(url, (err, res, body) => {
if (err) {
throw new Error(err);
};
const info = JSON.parse(body);
resolve(info);
});
});
};
I want to write a test for this function.
describe('getDataAboutWeather function', () => {
it('The request should success', () => {
const link = 'http://ip.jsontest.com';
expect(getDataAboutWeather(link)).to.eql({});
});
});
How to verify that my function works as it should?
Since at the moment I get an error.
AssertionError: expected {} to deeply equal {}
getDataAboutWeather is an asynchronous function which means it returns a Promise. I would change the test to:
it('The request should success', async () => {
const link = 'http://ip.jsontest.com';
expect(await getDataAboutWeather(link)).to.eql({});
});
To test your function, you need to check if the API JSON is equal to the expected JSON. You can use the function below.
_.isEqual(object, other);
More information on this: How to determine equality for two JavaScript objects?
I posted a similar question several days ago but I have made some changes and commenting on that question was becoming tedious, so it was recommended I ask a new question.
The idea is that I want to execute four equations synchronously. Inside those equations are HTTP requests. I have two of the equations working properly and but there is one equation that involves two POST requests and a GET requests. The second requests relies on the first and the third request relies on the second.
I have tried several different methods to get this to work. I have tried flattening my promises, returning the promises. All kinds of things, with no luck. I am not sure where I am going wrong.
Synchronous code snippet:
this.getData1(user, userID).then(() =>
{
this.getData2(user, userID)
.then(() =>
{
this.getData3(user, lan).then(() =>
{
this.userCheck(user);
})
});
});
I have getData2 and getData3 working.
getData1 looks like:
getData1(user: string, id: string){
console.log('grabbing CC information', id, user);
return new Promise((resolve, reject) =>
{
var status: string;
this._apiService.getAssertion(id).subscribe((data: any) =>
{
let assert = data.toString();
this._apiService.getToken(assert).subscribe((data: any) =>
{
let tkn = data.access_token.toString();
this._apiService.ccMeta(tkn, guid).subscribe((data: any) =>
{
parseString(data, (err, result) =>
{
if (err)
{
throw new Error(err);
}
else
{
status = result['entry']['content'][0]['m:properties'][0]['d:status'][0];
this.ccData.push(
{
key: 'userStatus',
value: status
})
}
});
});
});
});
resolve()
});
}
I also tried something like this previously. It did not work either.
apiService.getAssertion(id).then(assert =>
{
return apiService.getToken(assert.toString(), user);
}).then(data =>
{
return apiService.ccMeta(data.access_token.toString(), id);
}).then(parseStringPromise).then(information =>
{
this.ccData.push(
{
key: 'userStatus',
value: information.entry
});
});
Inside this function the getAssertion function is a POST request. The getToken function is another POST request that relies on the assertion from the first POST request. Finally, ccMeta is a get request that relies on the token from the second POST request.
I would expect getData1 to execute first, then getData2, then getData3, and finally, userCheck. Inside getData1 I need the assertion, then the token, and then get request to execute synchronously. The code snippet above is not executing correctly. The assertion is not properly being used in the getToken equation.
I would greatly appreciate some help.
Since these HTTP calls are in fact observables and not promises, I think you should look into observable composition using pipe and switchMap for instance. If you still want you method to return a promise, it could look like this:
getData1(user: string, id: string) {
console.log('grabbing CC information', id, user);
return new Promise((resolve, reject) => {
this._apiService.getAssertion(id)
.pipe(
switchMap((data: any) => {
let assert = data.toString();
return this._apiService.getToken(assert);
}),
switchMap((data: any) => {
let tkn = data.access_token.toString();
return this._apiService.ccMeta(tkn, guid);
}),
)
.subscribe(
data => {
parseString(data, (err, result) => {
if (err) {
reject(new Error(err));
return;
}
const status: string = result['entry']['content'][0]['m:properties'][0]['d:status'][0];
this.ccData.push({
key: 'userStatus',
value: status
});
resolve();
});
},
);
});
}
im using nodejs 8. I've replaced promise structure code to use async and await.
I have an issue when I need to return an object but await sentence resolve undefined.
This is my controller method:
request.create = async (id, params) => {
try {
let request = await new Request(Object.assign(params, { property : id })).save()
if ('_id' in request) {
Property.findById(id).then( async (property) => {
property.requests.push(request._id)
await property.save()
let response = {
status: 200,
message: lang.__('general.success.created','Request')
}
return Promise.resolve(response)
})
}
}
catch (err) {
let response = {
status: 400,
message: lang.__('general.error.fatalError')
}
return Promise.reject(response)
}
}
In http request function:
exports.create = async (req, res) => {
try {
let response = await Request.create(req.params.id, req.body)
console.log(response)
res.send(response)
}
catch (err) {
res.status(err.status).send(err)
}
}
I tried returning Promise.resolve(response) and Promise.reject(response) with then and catch in the middleware function and is occurring the same.
What's wrong?
Thanks a lot, cheers
You don't necessarily need to interact with the promises at all inside an async function. Inside an async function, the regular throw syntax is the same as return Promise.reject() because an async function always returns a Promise. Another thing I noticed with your code is that you're rejecting promises inside a HTTP handler, which will definitely lead to unexpected behavior later on. You should instead handle all errors directly in the handler and act on them accordingly, instead of returning/throwing them.
Your code could be rewritten like so:
request.create = async (id, params) => {
let request = await new Request(Object.assign(params, { property : id })).save()
if ('_id' in request) {
let property = await Property.findById(id)
property.requests.push(request._id)
await property.save()
}
}
And your http handler:
exports.create = async (req, res) => {
try {
await Request.create(req.params.id, req.body)
res.send({
status: 200,
message: lang.__('general.success.created','Request')
})
} catch (err) {
switch (err.constructor) {
case DatabaseConnectionError: // Not connected to database
return res.sendStatus(500) // Internal server error
case UnauthorizedError:
return res.sendStatus(401) // Unauthorized
case default:
return res.status(400).send(err) // Generic error
}
}
}
Error classes:
class DatabaseConnectionError extends Error {}
class UnauthorizedError extends Error {}
Because you have that try/catch block inside your http handler method, anything that throws or rejects inside the Request.create method will be caught there. See https://repl.it/LtLo/3 for a more concise example of how errors thrown from async function or Promises doesn't need to be caught directly where they are first called from.
I have the following 2 methods.
But in the get method that I'm overriding from the Http module, the authentication success callback is called after it has already executed the request and returned a response. This way it's adding
the JWT token to the headers in the wrong order, and too late.
I'm not super knowledgeable with promises and observables.. But what can I do so that it actually waits for the callback to be done before executing the request and returning the response?
authenticate(authCompletedCallback, errorCallback) {
let authContext = new Microsoft.ADAL.AuthenticationContext(AUTHORITY_URL);
authContext.tokenCache.readItems().then((items) => {
if (items.length > 0) {
AUTHORITY_URL = items[0].authority;
authContext = new Microsoft.ADAL.AuthenticationContext(AUTHORITY_URL);
}
// Attempt to authorize user silently.
authContext
.acquireTokenSilentAsync(RESOURCE_URL, CLIENT_ID)
.then(authCompletedCallback, () => {
// We require user credentials so trigger authentication dialog.
authContext
.acquireTokenAsync(RESOURCE_URL, CLIENT_ID, REDIRECT_URL)
.then(authCompletedCallback, errorCallback);
});
});
}
get(url: string, options?: RequestOptionsArgs): Observable<Response> {
this.authenticate((authResponse) => {
// This is executed second.
if (!options) {
options = { headers: new Headers() };
}
options.headers.set('Authorization', 'Bearer ' + authResponse.accessToken);
}, (err) => {
alert(JSON.stringify(err));
});
// This is executed first.
return super.get(url, options);
}
You might want to make the get function return a promise, since the actual Response object shouldn't exist until after you have authenticated. It looks like you're overriding a method, though, so maybe instead you'll want to make a different method.
getWithAuthentication(url: string, options?: RequestOptionsArgs): Promise<Observable<Response>> {
return new Promise((resolve, reject) => {
this.authenticate((authResponse) => {
if (!options) {
options = { headers: new Headers() };
}
options.headers.set('Authorization', 'Bearer ' + authResponse.accessToken);
let response = super.get(url, options);
resolve(response);
}, (err) => {
alert(JSON.stringify(err));
reject(err);
});
}
}