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);
});
Related
I am trying to write a lambda function in node 8.10 supported by aws lambda
this is with reference to my previous question
I am trying node async/await , same code work when I run it in my local(and is not even taking a second to get the response back) but when I am trying it in lambda getting time out error, here the code control is getting inside promise and printing url urltohit with the correct url but after it is getting stuck, have tried different thing like increasing the time in lambda config and even tried node request instead of http but still getting the same error.
'use strict';
var http = require('http');
var request = require('request');
exports.handler = async (event) => {
const response = await getRequest(urltohit);
console.log(response);
}
const getRequest = async (url) => {
console.log("Test log");
return new Promise((resolve, reject) => {
console.log(`url ${url}`);
http.get(url, function(res) {
console.log("Got response: " + res.statusCode);
resolve(res.statusCode);
}).on('error', function(e) {
console.log("Got error: " + e.message);
reject(e.message);
});
});
}
You need to call callback after function execution.
exports.handler = async (event, context, callback) => {
const response = await getRequest(urltohit);
console.log(response);
callback(null);
}
It's lambda behaviour to keep function running until callback called.
More could be found in official documentation
I would like to test my simple API that has /groups URL.
I want to make an API request to that URL (using Axios) before all tests begin and make the response visible to all test functions.
I am trying to make the response visible but not able to make it work. I followed a similar case with filling out the DB upfront but no luck with my case.
My simple test file below:
var expect = require('chai').expect
var axios = require('axios')
var response = {};
describe('Categories', function() {
describe('Groups', function() {
before(function() {
axios.get(config.hostname + '/groups').then(function (response) {
return response;
})
});
it('returns a not empty set of results', function(done) {
expect(response).to.have.length.greaterThan(0);
done();
})
});
});
I tried also a sligh modification of before function:
before(function(done) {
axios.get(config.hostname + '/groups')
.then(function (response) {
return response;
}).then(function() {
done();
})
});
but no luck too.
The error I am getting is simply that response isn't changing nor is visible within it. AssertionError: expected {} to have property 'length'
Summarising: How can I pass response from axios inside to in()?
Your first form is incorrect, because you're not returning the chained promise. As such, mocha has no way of knowing when your before is finished, or even that it's async at all. Your second form will solve this problem, but since axios.get already returns a promise, it's kind of a waste not to use mocha's built-in promise support.
As for making the response visible in the it, you need to assign it to a variable in a scope that will be visible within the it.
var expect = require('chai').expect
var axios = require('axios')
var response;
describe('Categories', function() {
describe('Groups', function() {
before(function() {
// Note that I'm returning the chained promise here, as discussed.
return axios.get(config.hostname + '/groups').then(function (res) {
// Here's the assignment you need.
response = res;
})
});
// This test does not need the `done` because it is not asynchronous.
// It will not run until the promise returned in `before` resolves.
it('returns a not empty set of results', function() {
expect(response).to.have.length.greaterThan(0);
})
});
});
I am trying to call a rest API from Firebase function which servers as a fulfillment for Actions on Google.
I tried the following approach:
const { dialogflow } = require('actions-on-google');
const functions = require('firebase-functions');
const http = require('https');
const host = 'wwws.example.com';
const app = dialogflow({debug: true});
app.intent('my_intent_1', (conv, {param1}) => {
// Call the rate API
callApi(param1).then((output) => {
console.log(output);
conv.close(`I found ${output.length} items!`);
}).catch(() => {
conv.close('Error occurred while trying to get vehicles. Please try again later.');
});
});
function callApi (param1) {
return new Promise((resolve, reject) => {
// Create the path for the HTTP request to get the vehicle
let path = '/api/' + encodeURIComponent(param1);
console.log('API Request: ' + host + path);
// Make the HTTP request to get the vehicle
http.get({host: host, path: path}, (res) => {
let body = ''; // var to store the response chunks
res.on('data', (d) => { body += d; }); // store each response chunk
res.on('end', () => {
// After all the data has been received parse the JSON for desired data
let response = JSON.parse(body);
let output = {};
//copy required response attributes to output here
console.log(response.length.toString());
resolve(output);
});
res.on('error', (error) => {
console.log(`Error calling the API: ${error}`)
reject();
});
}); //http.get
}); //promise
}
exports.myFunction = functions.https.onRequest(app);
This is almost working. API is called and I get the data back. The problem is that without async/await, the function does not wait for the "callApi" to complete, and I get an error from Actions on Google that there was no response. After the error, I can see the console.log outputs in the Firebase log, so everything is working, it is just out of sync.
I tried using async/await but got an error which I think is because Firebase uses old version of node.js which does not support async.
How can I get around this?
Your function callApi returns a promise, but you don't return a promise in your intent handler. You should make sure you add the return so that the handler knows to wait for the response.
app.intent('my_intent_1', (conv, {param1}) => {
// Call the rate API
return callApi(param1).then((output) => {
console.log(output);
conv.close(`I found ${output.length} items!`);
}).catch(() => {
conv.close('Error occurred while trying to get vehicles. Please try again later.');
});
});
I have a DialogFlow V2 node.js webhook.
I have an intent that is called with a webhook action:
const { WebhookClient } = require('dialogflow-fulfillment');
const app = new WebhookClient({request: req, response: res});
function exampleIntent(app) {
app.add("Speak This Out on Google Home!"); // this speaks out fine. no error.
}
Now, if I have an async request which finishes successfully, and I do app.add in the success block like this:
function exampleIntent(app) {
myClient.someAsyncCall(function(result, err) {
app.add("This will not be spoken out"); // no dice :(
}
// app.add("but here it works... so it expects it immediately");
}
... then Dialog Flow does not wait for the speech to be returned. I get the error in the Response object:
"message": "Failed to parse Dialogflow response into AppResponse, exception thrown with message: Empty speech response",
How can I make DialogFlow V2 wait for the Webhook's Async operations to complete instead expecting a speech response immediately?
NOTE: This problem only started happening in V2. In V1, app.ask worked fine at the tail-end of async calls.
exampleIntent is being called by the main mapper of the application like this:
let actionMap = new Map();
actionMap.set("my V2 intent name", exampleIntent);
app.handleRequest(actionMap);
And my async request inside myClient.someAsyncCall is using Promises:
exports.someAsyncCall = function someAsyncCall(callback) {
var apigClient = getAWSClient(); // uses aws-api-gateway-client
apigClient.invokeApi(params, pathTemplate, method, additionalParams, body)
.then(function(result){
var result = result.data;
var message = result['message'];
console.log('SUCCESS: ' + message);
callback(message, null); // this succeeds and calls back fine.
}).catch( function(error){
console.log('ERROR: ' + error);
callback(error, null);
});
};
The reason it worked in V1 is that ask() would actually send the request.
With V2, you can call add() multiple times to send everything to the user in the same reply. So it needs to know when it should send the message. It does this as part of dealing with the response from your handler.
If your handler is synchronous, it sends the reply immediately.
If your handler is asynchronous, however, it assumes that you are returning a Promise and waits till that Promise resolves before sending the reply. So to deal with your async call, you need to return a Promise.
Since your call is using Promises already, then you're in very good shape! The important part is that you also return a Promise and work with it. So something like this might be your async call (which returns a Promise):
exports.someAsyncCall = function someAsyncCall() {
var apigClient = getAWSClient(); // uses aws-api-gateway-client
return apigClient.invokeApi(params, pathTemplate, method, additionalParams, body)
.then(function(result){
var result = result.data;
var message = result['message'];
console.log('SUCCESS: ' + message);
return Promise.resolve( message );
}).catch( function(error){
console.log('ERROR: ' + error);
return Promise.reject( error );
});
};
and then your Intent handler would be something like
function exampleIntent(app) {
return myClient.someAsyncCall()
.then( function( message ){
app.add("You should hear this message": message);
return Promise.resolve();
})
.catch( function( err ){
app.add("Uh oh, something happened.");
return Promise.resolve(); // Don't reject again, or it might not send the reply
})
}
I want to write a function that returns a Boolean indicating whether an image with the specified public_id already exists in my Cloudinary space.
I can log the result to the console with the following code:
function isUploaded(public_id) {
cloudinary.api.resource(public_id, function(response){
console.log(response.hasOwnProperty('public_id'));
});
};
isUploaded('test');
However, I want to pass on the result, the Boolean, to another function. Using a return statement results in { state: 'pending' } being logged:
function isUploaded(public_id) {
return cloudinary.api.resource(public_id, function(response){
return response.hasOwnProperty('public_id');
});
};
console.log(isUploaded('test'));
This is has something to do with javascript Promises. I can't seem to restructure my code to make it work though. Any help would be much appreciated.
The problem is that cloudinary.api.resource runs asynchronously (which is why it requires a callback function).
You can make your isUploaded function return a Promise that resolves once that callback is called.
var cloudinary = require('cloudinary');
function isUploaded(public_id) {
return new Promise(function (resolve, reject) {
cloudinary.api.resource(public_id, function(response) {
var isUploaded = response.hasOwnProperty('public_id');
resolve(isUploaded);
});
});
};
isUploaded('test')
.then(function (result) {
console.log(result);
})
Note that api.resource() is rate limited (part of the Admin API) so this is not a "scalable" solution.
You can perform a HEAD request and get the statusCode of the response - 200 means the resource exists in your account, 404 otherwise.
For example -
var http = require('http')
var options = {method: 'HEAD', host:'res.cloudinary.com',path:'/<cloud_name>/image/upload/<yourimage.jpg>'}
var req = http.request(options, function(res) {console.log(res.statusCode);});
req.end();