Dialogflow v1 webhook - speech not returning immediately after an async call - node.js

I am using DialogFlow V1 node.js webhook and I have a problem.
I have an intent that will connect to the Spotify API to get tracks for my chatbot:
const {DialogflowApp} = require('actions-on-google');
function spotifyIntent(app) {
//Get search parameters
[...]
return asyncFunction(query).then(function (message) {
this.app.ask(message);
return Promise.resolve();
}).catch(function (err) {
this.app.ask("Une erreur est apparue: "+err);
return Promise.resolve();
})
}
with my async function being:
function asyncFunction(query) {
spotifyApi.clientCredentialsGrant()
.then(function(data) {
// spotifyApi.setAccessToken(data.body['access_token']);
return spotifyApi.searchTracks(query);
}).then(function(data) {
const link = data.body.tracks.items[0].preview_url;
let speech = '<speak>Here is the first result found.<audio src="'+ link +'">I didn\'t found it</audio></speak>';
return Promise.resolve(speech);
}).catch(function(err) {
return Promise.reject(err);
});
}
A Promise is hidden in the call of clientCredentialsGrant() and searchTracks();
My intent is called by the classic map with action: intentFunction.
I read here that the ask method should work but it doesn't for me. I tried the version with the Promise and with a callback but when I simulate my code I always get the answer:
"message": "Failed to parse Dialogflow response into AppResponse, exception thrown with message: Empty speech response
I don't understand what I'm doing wrong here? I know the problem come from the request being async but it should work fine with the Promise or the callback because right now it returns instantly?

It returns instantly because, although asyncFunction() calls something that returns a Promise, and although the then() and catch() portions return a Promise... asyncFunction() itself does not return a promise. In fact, it doesn't return anything explicitly, so it returns undefined.
You probably want that code to be
function asyncFunction(query) {
return spotifyApi.clientCredentialsGrant()
.then(function(data) {
// spotifyApi.setAccessToken(data.body['access_token']);
return spotifyApi.searchTracks(query);
}).then(function(data) {
const link = data.body.tracks.items[0].preview_url;
let speech = '<speak>Here is the first result found.<audio src="'+ link +'">I didn\'t found it</audio></speak>';
return Promise.resolve(speech);
}).catch(function(err) {
return Promise.reject(err);
});
}
Note the change in the first line that adds the return statement.
This is a common pattern (and a frequently overlooked problem) when dealing with async functions that need to return a Promise.

Related

NodeJS - Nested async functions

in an express router .post which is async, I have this line:
var recaptcha = await tokenValidate(req);
tokenValidate is below:
async function tokenValidate(req) {
// some code to generate a URL with a private key, public key, etc.
return await tokenrequest(url);
}
Then tokenrequest is below: (note request is the npm request library)
async function tokenrequest(url) {
request(url, function(err, response, body){
//the body is the data that contains success message
body = JSON.parse(body);
//check if the validation failed
if(body.success !== undefined && !body.success){
return false;
}
//if passed response success message to client
return true;
})
}
Issue is the nested async functions. The initial recaptcha variable returns undefined since 'request' doesn't resolve, even though I'm using await. How can I get recaptcha to wait for tokenValidate which in turn has to wait for tokenrequest?
Thank you.
A couple issues:
Your tokenrequest function is not returning anything (the return value in the request callback function won't be returned)
await tokenrequest(url); won't work as you expect because the request library is using a callback (not async/await)
This can be fixed by returning a Promise:
async function tokenrequest(url) {
return new Promise(function (resolve, reject) {
request(url, function (err, response, body) {
//the body is the data that contains success message
body = JSON.parse(body);
//check if the validation failed
if (body.success !== undefined && !body.success) {
resolve(false);
} else {
//if passed response success message to client
resolve(true);
}
});
});
}
Your tokenRequest() function is returning a promise (because it's async), but that promise resolves immediately with no value that is attached to your call to request(). And, the return values you do have are just returning to the request() callback where they are ignored, not from your actual top level function.
What I would suggest is that you ditch the request() module because it's deprecated and does not support promises and pick a new more modern module that does support promises. I use the got() module which would make this whole thing a LOT easier and a lot fewer lines of code.
const got = require('got');
async function tokenrequest(url) {
let result = await got(url).json();
return !!result.success;
}

Async - Await issue using Twitter API

I'm currently trying to practice using an API with Twitter API.
I'm using Twit package to connect to twitters API but when I try to do a get request I get
Promise { pending }
I have tried using Async-Await but I'm not sure what I'm doing wrong here.
Here is my code:
const Twit = require('twit');
const twitterAPI = require('../secrets');
//Twit uses OAuth to stablish connection with twitter
let T = new Twit({
consumer_key: twitterAPI.apiKey,
consumer_secret: twitterAPI.apiSecretKey,
access_token: twitterAPI.accessToken,
access_token_secret: twitterAPI.accessTokenSecret
})
const getUsersTweets = async (userName) => {
let params = { screen_name: userName, count: 1 }
const userTweets = await T.get('search/tweets', params, await function (err, data, response) {
if (err) {
return 'There was an Error', err.stack
}
return data
})
return userTweets
}
console.log(getUsersTweets('Rainbow6Game'));
Problem
The biggest assumption that is wrong with the sample code is that T.get is expected to eventually resolve with some data.
const userTweets = await T.get('search/tweets', params, await function (err, data, response) {
if (err) {
return 'There was an Error', err.stack
}
return data // 'data' returned from here is not necessarily going to be received in 'userTweets' variable
})
callback function provided as last argument in T.get function call doesn't have to be preceded by an 'await'.
'data' returned from callback function is not necessarily going to be received in 'userTweets' variable. It totally depends on how T.get is implemented and can not be controlled.
Reason
Thing to be noted here is that async / await works well with Promise returning functions which eventually get resolved with expected data, however, that is not guaranteed here
Relying on the result of asynchronous T.get function call will probably not work because it returns a Promise { pending } object immediately and will get resolved with no data. The best case scenario is that everything with your function will work but getUsersTweets function will return 'undefined'.
Solution
The best solution is to make sure that your getUsersTweets function returns a promise which eventually gets resolved with correct data. Following changes are suggested:
const getUsersTweets = (userName) => {
return new Promise ((resolve, reject) => {
let params = { screen_name: userName, count: 1 }
T.get('search/tweets', params, function (err, data, response) {
if (err) {
reject(err);
}
resolve(data);
})
}
}
The above function is now guaranteed to return expected data and can be used in the following way:
const printTweets = async () => {
const tweets = await getUsersTweet(userName);
console.log(tweets);
}
printTweets();
From what I can see on your code, getUserTweets is an async function, so it will eventually return a promise. I'm assuming you will use this value on another function, so you will need to use it inside an async function and use await, otherwise you will always get a promise.
const logTweets = async (username) => {
try {
const userTweets = await getUsersTweets(username);
// Do something with the tweets
console.log(userTweets);
catch (err) {
// catch any error
console.log(err);
}
}
If logging is all you want and you wrapped it inside a function in which you console.log it, you can call that function directly:
logTweets('someUsername');

simple typescript function with axios get call does not work

I am learning to do http calls from node/typescript application. I have the following method, using npm package Axios to do a http get call to a fake api end point.
public GetTrailById(trailId: number) : string {
console.log("Entered the method.");
const res = axios.get("https://reqres.in/api/users?page=2")
.then((res: { data: any; }) => {
console.log("inside then");
return "this is good, got response";
})
.catch((err: { response: any; }) => {
console.log("inside catch");
return "this is not good, inner catch";
});
console.log("nothing worked");
return "nothing worked";
}
When I run the above code, I do see the following console outputs, but no console output from either the then block or the catch block. I dont understand where the control goes.
Output I see:
Entered the method.
nothing worked
What I expect to see:
Entered the method.
inside then //or inside catch
Can someone help me figure out what I am doing wrong here?
You're assigning your promise to the variable res but not doing anything with it.
You probably want something more like:
async function getTrailById(trailId: number): Promise<string> {
console.log("Entered the method.");
try {
const res = await axios.get("https://reqres.in/api/users?page=2");
console.log("inside then");
return res.data;
} catch {
console.log("inside catch");
return "this is not good, inner catch";
}
}
// ...in some other async function
const trail = await getTrailById(trailId)
Please note that I changed the return type to Promise<string> (which is what you want here since it's async) and I made the function name start with a lower-case letter (camelCase is normally used for variable and function names in JavaScript/TypeScript).

agent.add() is not working sometimes when intent is having a big logic and the api call using Axios

I am having few intents in my dialogflow agent for which I have written function using the inline editor of Dialogflow so in few intents, I am calling some APIS using Axios with some kind of logic and for this such intent, it is not able to prompt message on test console sometimes but easily printing logs in the console.
I am attaching some dummy code -:
function someintent(agent){
return new Promise((resolve, reject) => { // Tried using promises as well but didn't work
const parameter1 = agent.parameters.parameter1;
const parameter2 = agent.parameters.parameter2;
// Some of the code is just logic, Problem mainly occurs at line no 39
if (parameter1 != ''){
if (parameter1.toString().length !=9){
agent.add('Please enter valid 9 digit number');
agent.context.set({
'name':'someintent-followup',
'lifespan': 0
});
}
else{
if(isNaN(parameter1)){
agent.add('Please enter integer');
agent.context.set({
'name':'previous-followup'
});
}
/**
* Main area which creates problem is below code.
*
*/
else{
agent.add('What is the nature of your Business ?'); // This is not working sometimes
return axios.post('http://some-test-api/',
{"parameters1": parameter1}).then((result) => {
console.log(result.data);
console.log('Store data to api');
//agent.add('What is the nature of your Business ?'); // I tried here also which is also not working sometimes
});
}
}
}
else{
agent.add('What is the nature of your Business ?');
return axios.post('http://some-test-api/',
{"parameters2": parameter2}).then((result) => {
console.log(result.data);
console.log('Store data to api');
});
}
resolve(); // Resolving promise
}
As per my understanding, the issue is if the intent is having a quite big logic and that too with some API Call then it has timeout and callback function issue (maybe) which creates a problem of not printing response in the interface(test console)
I really required help with this. Thanks in advance.
It sounds like there are two potential issues you have:
The first is that it sounds like you're concerned that the async call with axios will take "too long". This may be a concern if the call plus your other processing takes more than a few seconds.
But what looks more likely is that how you're making your call with axios and using Promises is causing an issue.
With the code you're showing, you're returning the new Promise object, but in the execution code you're returning the axios Promise, so the call to resolve() never gets made. You should only be calling resolve() when the async operation has, itself, resolved.
But since axios.post() returns a Promise, you don't need to wrap it in your own Promise. You just need to return the Promise/then chain that is part of each axios call.
So your code might look something like (untested):
function someintent(agent){
const parameter1 = agent.parameters.parameter1;
const parameter2 = agent.parameters.parameter2;
if (parameter1 != ''){
if (parameter1.toString().length !=9){
agent.add('Please enter valid 9 digit number');
agent.context.set({
'name':'someintent-followup',
'lifespan': 0
});
}else{
if(isNaN(parameter1)){
agent.add('Please enter integer');
agent.context.set({
'name':'previous-followup'
});
}else{
return axios.post('http://some-test-api/',{"parameters1": parameter1})
.then((result) => {
console.log(result.data);
console.log('Store data to api');
agent.add('What is the nature of your Business ?');
});
}
}
}else{
return axios.post('http://some-test-api/',{"parameters2": parameter2})
.then((result) => {
console.log(result.data);
console.log('Store data to api');
agent.add('What is the nature of your Business ?');
});
}
}

Get async value from firestore

I am struggling with async operations. I am trying to simply get a value from firestore and storing it in a var.
I manage to receive the value, I can even save it in the var when I do that specifically (use the var within the get function) but I don't seem to manage the await properly when trying to save this in a flexible way:
async function getValues(collectionName, docName,) {
console.log("start")
var result;
var docRef = await db.collection(collectionName).doc(docName).get()
.then(//async// (tried this as well with async) function (doc) {
if (doc.exists) {
console.log("Document data:", doc.data());
result = doc.data().text;
console.log(result);
return //await// (this as well with async) result;
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
result = "No such document!";
return result;
}
console.log("end");
}).catch (function (err) {
console.log('Error getting documents', err);
});
};
helpMessage = getValues('configuration','helpMessage');
Note: doc.data().text -> "text" is the name of the field where my value is stored in. Do I have to use .value here?
The result I get in the console is: info: Document data: { text: 'The correct text from the database' }
info: The correct text from the database
But using helpMessage in my code I get {}
Image from the Telegram bot where I am trying to use the helpMessage as a response to the '/help' command.
I have checked: getting value from cloud firestore,
Firebase Firestore get() async/await, get asynchronous value from firebase firestore reference and most importantly How do I return the response from an asynchronous call?. They either deal with multiple documents (using forEach), don't address the async nature of my problem or (last case), I simply fail to understand the nature of it.
Additionally, both nodejs and firestore seems to be developing rapidly and finding good, up-to-date documentation or examples is difficult. Any pointers are much appriciated.
You have things the wrong way around. It's much easier than you think it is.
function getValues(collectionName, docName) {
return db.collection(collectionName).doc(docName).get().then(function (doc) {
if (doc.exists) return doc.data().text;
return Promise.reject("No such document");
}};
}
If a function returns a promise (like db.collection(...).doc(...).get()), return that promise. This is the "outer" return above.
In the promise handler (inside the .then() callback), return a value to indicate success, or a rejected promise to indicate an error. This is the "inner" return above. Instead of returning a rejected promise, you can also throw an error if you want to.
Now you have a promise-returning function. You can use it with .then() and .catch():
getValues('configuration','helpMessage')
.then(function (text) { console.log(text); })
.catch(function (err) { console.log("ERROR:" err); });
or await it inside an async function in a try/catch block, if you like that better:
async function doSomething() {
try {
let text = await getValues('configuration','helpMessage');
console.log(text);
} catch {
console.log("ERROR:" err);
}
}
If you want to use async/await with your getValues() function, you can:
async function getValues(collectionName, docName) {
let doc = await db.collection(collectionName).doc(docName).get();
if (doc.exists) return doc.data().text;
throw new Error("No such document");
}
Since getValues function returns a promise, you need to await getValues function while calling it.
Change getValues like so -
function getValues(collectionName, docName,) {
console.log("start")
var result;
return db.collection(collectionName).doc(docName).get()
.then(function (doc) {
if (doc.exists) {
console.log("Document data:", doc.data());
result = doc.data().text;
console.log(result);
return result;
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
result = "No such document!";
return result;
}
}).catch (function (err) {
console.log('Error getting documents', err);
});
};
Then use getValues like so -
helpMessage = await getValues('configuration','helpMessage');
Explanation -
async, await are just syntactic sugar for Promises. async functions return a promise (or AsyncFunction more accurately) which needs to be resolved to use its enclosed value.
See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
Finally managed to get it working. Thanks for the input Tomalak!
getValues(help.collectionName, help.docName)
.then((text) => {
console.log(text);
help.message = text;
})
.catch((err) => { console.log("Error: ", err); });
function getValues(collectionName, docName) {
return db.collection(collectionName).doc(docName).get().then((doc) => {
if (doc.exists) {
return doc.data().text;
}
else {
return Promise.reject("No such document");
}});
}
bot.help((ctx) => ctx.reply(help.message));
Unfortunately, I can not pin-point the exact reason this worked. Some little fixes (missed comma in the console.log) and formatting definitely helped me understanding the structure though. Hope someone else finds this useful, when starting to play around with node and firebase.

Resources