Update the variable value after asynchronous http call - node.js

I have a firebase function called getRequest which contains simple http calls using request npm package and a variable named result which will should contain the body response from the http call after request completed.
However, the request output is a "this should be replaced" string, because the http call runs asynchronously.
How do you get the result variable to contain the body response from the http call?
const functions = require('firebase-functions');
const request = require('request');
exports.getRequest = functions.https.onRequest(() => {
let result = "this should be replaced";
request('http://worldclockapi.com/api/json/est/now', function(error,response,body){
if (!error && response.statusCode == 200)
result = body;
});
console.log(result);
});
I've tried to use callback but I'm confused to put the parameter, because actually this is also inside the callback.

request supports callback interfaces natively but does not return a promise. You must wait the asynchronous call to the external API is finished before sending back the response, and for this you should use a Promise which will resolves when the call to the API returns.
You can use the request-promise library and the rp() method which "returns a regular Promises/A+ compliant promise" and then, adapt your code as follows:
const functions = require('firebase-functions');
const rp = require('request-promise');
exports.getRequest = functions.https.onRequest((req, res) => {
let result = "this should be replaced";
var options = {
uri: 'http://worldclockapi.com/api/json/est/now',
json: true // Automatically stringifies the body to JSON
};
rp(options)
.then(parsedBody => {
result = parsedBody.currentDateTime;
console.log(result);
res.send( {result} );
})
.catch(err => {
// API call failed...
res.status(500).send({'Error': err});
});
});
I would suggest you watch the official Video Series (https://firebase.google.com/docs/functions/video-series/) which explains the key point about returning Promises and also how to handle errors in an HTTP Cloud Function.
Two extra points to note:
onRequest() arguments
You need to pass two arguments to the onRequest() function: the Request and theResponse` objects, see https://firebase.google.com/docs/functions/http-events?authuser=0#trigger_a_function_with_an_http_request
exports.date = functions.https.onRequest((req, res) => {
// ...
});
Pricing plan
You need to be on the "Flame" or "Blaze" pricing plan.
As a matter of fact, the free "Spark" plan "allows outbound network requests only to Google-owned services". See https://firebase.google.com/pricing/ (hover your mouse on the question mark situated after the "Cloud Functions" title)
Since the worldclock API is not a Google-owned service, you need to switch to the "Flame" or "Blaze" plan.

the callback calls asynchronous, so console.log(result) call before the callback run.
if you want to print the result variable with content from the requet you need to print it from the callback
const functions = require('firebase-functions');
const request = require('request');
exports.getRequest = functions.https.onRequest(() => {
let result = "this should be replaced";
request('http://worldclockapi.com/api/json/est/now', function(error,response,body){
if (!error && response.statusCode == 200)
result = body;
console.log(result);
});
});
however, I recommend request-promise and use async/await syntax
const functions = require('firebase-functions');
const request = require('request-promise');
exports.getRequest = functions.https.onRequest(async () => {
let result = "this should be replaced";
result = await request('http://worldclockapi.com/api/json/est/now');
console.log(result);
});

Related

Make HTTP GET from fulfillment in Node.JS

I'm trying to make my own google action and I want to call an external api to get responses.
Here is my code:
const { conversation } = require('#assistant/conversation');
const functions = require('firebase-functions');
const app = conversation({debug:true});
const https = require('https');
app.handle('Tester', conv => {
// Implement your code here
conv.add("ok it works");
});
app.handle('Tester2', conv => {
// Implement your code here
let url = 'https://jsonplaceholder.typicode.com/users?_limit=2';
//const url = "https://samples.openweathermap.org/data/2.5/weather?q=London,uk&appid=b6907d289e10d714a6e88b30761fae22";
http_req(url).then((message)=> {
console.log(message[0].name);
conv.add(message[0].name);
//return Promise.resolve();
});
});
function http_req(url) {
return new Promise((resolve, reject) => {
https.get(url, function(resp) {
var json = "";
resp.on("data", function(chunk) {
//console.log("received JSON response: " + chunk);
json += chunk;
});
resp.on("end", function() {
let jsonData = JSON.parse(json);
console.log(jsonData[0].name);
resolve(jsonData);
});
}).on("error", (err) => {
reject("Error: " + err.message);
});
});
}
exports.ActionsOnGoogleFulfillment = functions.https.onRequest(app);
The logs:
Error text:
Error: Response has already been sent. Is this being used in an async call that was not returned as a promise to the intent handler?
The problem is that the assistant won't say the conv.add(message[0].name); (obviusly it has a value)
Thanks in advance!
Thanks to a reddit user
https://www.reddit.com/r/GoogleAssistantDev/comments/lia5r4/make_http_get_from_fulfillment_in_nodejs/gn23hi3?utm_source=share&utm_medium=web2x&context=3
This error messages tells you just about all you need to know! Your
call to con.add() is indeed being used in an asynchronous call (the
callback chained to the Promise you created from http_req), and you
are indeed not returning that Promise.
Here's what's happening:
Google calls your 'Tester2' handler
You start an asynchronous HTTP request via http_req, encapsulated in a
Promise
Your function completes before the HTTP request does
Google sees that you are not returning anything from your handler and
assumes that you're done, so it sends the Response
The HTTP request finishes and its Promise resolves, calling your code
attached by the then() function
The simple solution here is to return the Promise created by your
http_req(...).then(...) code, so Google will know that you're not just
quite done, and it should wait for that Promise to resolve before
sending the Response.
If you can use async/await it becomes a bit clearer:
app.handle('Tester2', async conv => {
// Implement your code here
let url = 'https://jsonplaceholder.typicode.com/users?_limit=2';
//const url = "https://samples.openweathermap.org/data/2.5/weather?q=London,uk&appid=b6907d289e10d714a6e88b30761fae22";
const message = await http_req(url);
console.log(message[0].name);
conv.add(message[0].name);
});

How to evaluate API response from server and act accordingly at client side using Fetch() and Node.js

I fetch data at server side and push the result to global variable and then send that global variable to client with app.post method using Express.js. My problem is that client fetches the global variable too soon without the data received from the API first. How can I evaluate the response so that client would wait the global variable to reveive data first before displaying anything.
Server side, code looks something like this:
let sharpe = ''
app.post('/api', async(req, res, next) => {
console.log('I got a request!')
thisOne = req.body.stock1
thisOne2 = req.body.stock2
var result = await setup();
res.json({
status: 'success',
stocks: sharpe
});
})
Sharpe is the global variable storing the response from multiple API calls and is the one that should be sent back to client. Client side code is this:
const sendData = async (event) => {
event.preventDefault();
var stock1 = document.getElementById('weight1').value
var stock2 = document.getElementById('weight2').value
const data = {stock1, stock2};
const options = {
method: 'POST',
body: JSON.stringify(data),
headers: {'Content-Type': 'application/json' }
}
fetch('/api', options).then(res => res.json()).then(res => {
console.log(res.stocks);
})
}
As a result SendData() function fetches the sharpe variable that is empty at the moment. How can I adjust client side code or server side code that the client waits for a correct response? Thanks.
One solution would be to store the API results to database and client would fetch ready made datastream but is there more straightforward solution?
To wait for your API Server to set the sharpe Parameter, it needs to be awaited, which you already did. It depends on the setup function (for example setup()) which fills the sharpe parameter. It has to return a promise, which is resolved once sharpe is filled with the data.
let sharpe = ''
async setup() {
return new Promise((resolve, reject) => {
sharpe = 'test';
// set value for sharpe
resolve()
})
}
app.post('/api', async(req, res, next) => {
console.log('I got a request!')
thisOne = req.body.stock1
thisOne2 = req.body.stock2
var result = await setup();
res.json({
status: 'success',
stocks: sharpe
});
})
Eventually it starded working when I pushed all the API calls in the app.post middleware at the server side using promise chaining. The initial problem remains a mystery.

How do I pass parameters to cors within a firebase function

I'm trying to send basic string parameters to cors which is within a firebase function. However I can't find a way to pass the parameters without the cors flaring up and blocking my request.
const functions = require('firebase-functions');
const cors = require('cors')({origin:true})
exports.sendMail = functions.https.onRequest((request, response) => {
cors(request, response,() =>{
var variableA = request.body.A
var variableB = request.body.B
var variableC = reques.body.C
response.send("Function run completed")
})
The Firebase function is being called using an axios.get()
axios.get('someURL', {
A:variableA,
B:variableB,
C:variableC
})
.then(res => {
const output = res.data;
console.log("message output = " + output)
})
The request and firebase function are working correctly and as expected. But I don't know how to pass the parameters through properly. Anyone know how to do this?
IMPORTANT EDIT! - I should add that the method shown in the code above results in the parameters being undefined. If I try to reference the request and response objects from the axios call in any way (the first pair of request and response objects), that is when the cors error flares up.
You should use axios.post(): by doing axios.get() the body is not passed to the Cloud Function.
axios.post('someURL', {
A:variableA,
B:variableB,
C:variableC
})
.then(res => {
const output = res.data;
console.log("message output = " + output)
})

response.send is not a function

I am writing a firebase function for the webhook fulfillment of dialogflow chatbot. It keeps generating error that response.send is not a function
const functions = require('firebase-functions');
var request1 = require('request')
exports.webhook = functions.https.onRequest((request, response) => {
console.log("request.body.result.parameters: ", request.body.result.parameters);
let params = request.body.result.parameters;
var options = {
url: `https://islam360api.herokuapp.com/${params.find}`,
json:true
}
request1(options, function(error, response, body){
if(error) response.send({speech: "error in API call"});
else response.send({speech: body.speech});
});
});
Firebase Logs
Problem: this is a problem of shadow variable name, when you are trying to send response using firebase functions response object, in fact you are sending response back on response object of npm request module which is ofcourse not possible
Solution:
just put an underscore or change the spelling and you are ready to go, have a look of code:
(notice change in 5th line from bottom)
const functions = require('firebase-functions');
var request1 = require('request')
exports.webhook = functions.https.onRequest((request, response) => {
console.log("request.body.result.parameters: ", request.body.result.parameters);
let params = request.body.result.parameters;
var options = {
url: `https://islam360api.herokuapp.com/${params.find}`,
json:true
}
request1(options, function(error, _response, body){
if(error) response.send({speech: "error in API call"});
else response.send({speech: body.speech});
});
});

Firebase cloud functions inconsistent with bulk updates

I have my firebase cloud function in which I am calling my external api end point like this.
const functions = require('firebase-functions');
var admin = require("firebase-admin");
admin.initializeApp(functions.config().firebase);
var request = require('request');
var moment = require('moment');
var rp = require('request-promise');
var db = admin.database();
exports.onCheckIn = functions.database.ref('/news/{newsId}/{entryId}')
.onCreate(event => {
console.log("Event Triggered");
var eventSnapshot = event.data.val();
request.post({
url: 'http://MyCustomURL/endpoint/',
form: {
data: eventSnapshot.data
}
}, function(error, response, body){
console.log(response);
});
})
I am using Blaze plan and this is working completely fine. But the problem is that when I am creating bulk data (around 50 to 100 entries) the HTTP request at to my custom url is not working properly.One or two HTTP request are being skipped.
I have debugged my custom server and found out that it is not receiving missing requests from firebase. But I have also checked the cloud function logs and I can find that every event is correctly being triggered.
What could be the problem? Am I doing anything wrong ?
You're not returning any value from your function. This means that Cloud Functions assumes that the function is done with its work after the last statement has run. But since you're making an asynchronous HTTP call in the function, that calls may not have completed yet. Unfortunately you're not telling Cloud Functions about the fact that you have an outstanding call, so it may kill your function at any time after you return.
The solution is to return a promise that resolves after the HTTP request has completed. Since you're already including request-promise this is simple:
exports.onCheckIn = functions.database.ref('/news/{newsId}/{entryId}')
.onCreate(event => {
console.log("Event Triggered");
var eventSnapshot = event.data.val();
return rp.post({
url: 'http://MyCustomURL/endpoint/',
form: {
data: eventSnapshot.data
}
});
})
This is a common problem for developers new to JavaScript, or with JavaScript and Firebase, and is covered in:
the Firebase documentation
this blog post
this video

Resources