I'm trying to display data from a Firebase database in Dialogflow responses.
This is my code:
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
const agent = new WebhookClient({ request, response });
function getArtikel (agent) {
return db.collection('artikel').get()
.then(doc => {
db.collection('artikel').get().then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
console.log(doc.id, " => ", doc.data());
return agent.add(doc.data());
});
});
return Promise.resolve('Search finished');
}).catch(() => {
agent.add('Error');
});
}
let intentMap = new Map();
intentMap.set('GetArtikel', getArtikel);
agent.handleRequest(intentMap);
});
Unfortunately, the agent only returns a empty response but the log contains the data as expected. What am I doing wrong, here?
It returns an empty response because db.collection('artikel').get() returns a promise. It means it runs asynchronously. If you logged on Chrome, you can see the result after expanding the array (look at the [i] icon)
You can simply fix the problem by adding async to your function and await after return:
async function getArtikel (agent)
return await db.collection('artikel').get()
Check out this video for more information on async/await
Related
I'm new in JS world and callbacks.
Why I can't return response after then function for the Firebase callable functions?
It returns empty if I return like shown below. I guess it doesn't wait for the response, response has data.output variable actually.
exports.testApi = functions.https.onCall(async(data, context) => {
const formData = new FormData();
formData.append("height", "512");
const response = await axios.post('https://....', formData, {
headers: formData.getHeaders()
})
.then((response) => {
console.log(response.data);
return {'imageURL':response.data.output};
})
.catch((error) => {
console.log(error)
});
}
);
It works with this format
const response = await axios.post('https://..', formData, {
headers: formData.getHeaders()
})
return {'imageURL':response.data.output};
The main attraction of async and await is cleaner syntax, in particular syntax that doesn't use then (opinion). You can mix them but I would always try to avoid it, especially here where the task is so simple.
exports.testApi = functions.https.onCall(async(data, context) => {
try {
const formData = new FormData();
formData.append("height", "512");
// Wait for post to give us a response.
const response = await axios.post('https://....', formData, {
headers: formData.getHeaders()
});
// If we get here, we waited and got a response. Proceed.
console.log(response.data);
return {'imageURL': response.data.output};
} catch (error) {
// If we get here, post threw an error (assuming it throws).
// And we never executed any lines after const response = await...
console.log(error);
}
});
Side note: Firebase Cloud Functions have to be properly terminated and this function does not. For example, if post throws an error and control flows to the catch block then the function will simply timeout because we haven't returned a Promise or thrown a compliant error.
I have always been made to believe that async and await and promises were one and the same thing, here, in my code, res.json() is working with await but isnt working at all with .then(). maybe I am missing something. please this is not a duplicate question. I have been scouring the internet for answers for the most part of two days now.
Here is the code with .then, aync and await, and i will also include the code from my server that sends the json
CODE USING .then()
const requestURL = "/auth/profile";
const request = new Request(requestURL, {
method: 'POST'
});
fetch(request)
.then( (res) => {
res.json()
})
.then(obj => {
console.log(obj)
})
.catch(err => {
console.log(err)
})
here, console.log(obj) logs undefined
CODE USING ASYNC AND AWAIT
const requestURL = "/auth/profile";
const request = new Request(requestURL, {
method: 'POST'
});
fetch(request)
.then( aync (res) => {
const obj = await res.json()
console.log(obj)
})
.catch(err => {
console.log(err)
})
here, console.log(obj) logs the correct json object as expected
server request handler (nodejs)
const getProfile = async (req, res, next) => {
const result = JSON.stringify({"status":"not logged in", "body": "there is a big poblem"})
res.setHeader('content-type', 'application/json');
res.status(200).send(result)
}
While I have used other tools to make sure I have the response from my server properly set up, if i am missing something kindly indicate to me, cheers
The problem is in your handling of of the chains. You need to return data to the next then, it's not automatic and varies depending on scope. Change to:
fetch(request)
.then( (res) => {
// Process.. then return some data to the next chain in line.
return res.json()
})
.then(obj => {
// Now, obj will be what the line above 'returned'
console.log(obj)
})
.catch(err => {
console.log(err)
})
Trying to read a pushToken from a given user in the users collection (after an update operation on another collection) returns undefined
exports.addDenuncia = functions.firestore
.document('Denuncias/{denunciaID}')
.onWrite((snap, context) => {
const doc = snap.after.data()
const classificadoId = doc.cid
const idTo = doc.peerId
db.collection('Classificados').doc(classificadoId)
.update({
aprovado: false
})
.then(r => {
getToken(idTo).then(token => {
// sendMsg...
})
}).catch(updateErr => {
console.log("updateErr: " + updateErr)
})
async function getToken(id) {
let response = "getTokenResponse"
console.log("id in getToken: " + id)
return db.collection('users').doc(id).get()
.then(user => {
console.log("user in getToken: " + user.data())
response = user.data().pushToken
})
.catch(e => {
console.log("error get userToken: " + e)
response = e
});
return response
}
return null
});
And this is from the FB console log:
-1:43:33.906 AM Function execution started
-1:43:36.799 AM Function execution took 2894 ms, finished with status: 'ok'
-1:43:43.797 AM id in getToken: Fm1RwJaVfmZoSgNEFHq4sbBgoEh1
-1:43:49.196 AM user in getToken: undefined
-1:43:49.196 AM error get userToken: TypeError: Cannot read property 'pushToken' of undefined
-1:43:49.196 AM returned token: undefined
And we can see in this screenshot from the db that the doc does exist:
Hope someone can point me to what I'm doing wrong here.
added screenshot of second example of #Renaud as deployed:
As Doug wrote in his comment, you need to "return a promise from the top level function that resolves when all the async work is complete". He also explains that very well in the official video series: https://firebase.google.com/docs/functions/video-series/ (in particular the 3 videos titled "Learn JavaScript Promises"). You should definitely watch them, highly recommended!
So, the following modifications to your code should work (untested):
exports.addDenuncia = functions.firestore
.document('Denuncias/{denunciaID}')
.onWrite(async (snap, context) => { // <- note the async keyword
try {
const doc = snap.after.data()
const classificadoId = doc.cid
const idTo = doc.peerId
await db.collection('Classificados').doc(classificadoId)
.update({
aprovado: false
});
const userToSnapshot = await db.collection('users').doc(idTo).get();
const token = userToSnapshot.data().pushToken;
await sendMsg(token); // <- Here you should take extra care to correctly deal with the asynchronous character of the sendMsg operation
return null; // <-- This return is key, in order to indicate to the Cloud Function platform that all the asynchronous work is done
} catch (error) {
console.log(error);
return null;
}
});
Since you use an async function in your code, I've used the async/await syntax but we could very well write it by chaining the promises with the then() method, as shown below.
Also, I am not sure, in your case, that it adds any value to put the code that gets the token in a function (unless you want to call it from other Cloud Functions but then you should move it out of the addDenuncia Cloud Function). That's why it has been replaced by two lines of code within the main try block.
Version with chaining promises via the then() method
In this version we chain the different promises returned by the asynchronous methods with the then() method. Compared to the async/await version above, it shows very clearly what means "to return a promise from the top level function that resolves when all the asynchronous work is complete".
exports.addDenuncia = functions.firestore
.document('Denuncias/{denunciaID}')
.onWrite((snap, context) => { // <- no more async keyword
const doc = snap.after.data()
const classificadoId = doc.cid
const idTo = doc.peerId
return db.collection('Classificados').doc(classificadoId) // <- we return a promise from the top level function
.update({
aprovado: false
})
.then(() => {
return db.collection('users').doc(idTo).get();
})
.then(userToSnapshot => {
if {!userToSnapshot.exists) {
throw new Error('No document for the idTo user');
}
const token = userToSnapshot.data().pushToken;
return sendMsg(token); // Again, here we make the assumption that sendMsg is an asynchronous function
})
.catch(error => {
console.log(error);
return null;
})
});
I tried everything , I have this cloud function (that otherwise works) :
exports.contentServer = functions.https.onRequest((request, response) => {
admin.database().ref('/list/' + "abc").once('value').then(function(snapshot) {
console.log(snapshot.val() );
return null;
}).catch(function(error) {
console.log("Error getting document:", error);
return response.send(error);
});
});
or also this :
admin.database().ref('/list').once('value').then(function(snapshot) {
var event = snapshot.val();
app.tell('Result: '+event);
});
and this :
exports.contentServer = functions.https.onRequest((request, response) => {
var db = admin.database();
db.ref("list/abc").once("value").then(snap => {
var store = snap.val().description;
return store;
}).then(() => {
var store = snap.val().description;
return store;
}).then(snap => {
var store = snap.val().description;
return store;
}).catch(err => {
console.log(err);
response.send("error occurred");
});
});
and always get back the error :
"Could not handle the request"
Or I get error on deploy that :
Each then() should return a value or throw
I have a collection called list, inside I have a document named "abc".
Is there something I have to include ? something I have to setup in Firebase to make it work ? anything basic nobody write on the docs ?
Modified following the comments above explaining the OP uses Firestore and not the Realtime Database
You should do as follows. You have to wait that the promise returned by the get() method resolves before sending back the response. For this you need to use the then() method, see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then
exports.contentServer = functions.https.onRequest((request, response) => {
admin.firestore().collection('list').doc('abc').get()
.then(docSnapshot => {
console.log(docSnapshot.data());
return response.send(docSnapshot.data()); // or any other value, like return response.send( {result: "success"} );
})
.catch(error => {
console.log("Error getting document:", error);
return response.status(500).send(error);
});
});
As written in the comments above, I would suggest that you watch the 3 videos about "JavaScript Promises" from the Firebase video series: https://firebase.google.com/docs/functions/video-series/
Try this
Updated. Return the response inside then() as what #Renaud Tarnec pointed out.
Using realtime database
exports.contentServer = functions.https.onRequest((request, response) => {
var database = admin.database();
database.ref('list').child('abc').once("value", snapshot => {
const data = snapshot.val();
return response.send(data);
}).catch(error => {
return response.status(500).send(error);
});
});
If you are using firestore.
exports.contentServer = functions.https.onRequest((request, response) => {
const firestore = admin.firestore();
firestore.collection("list").doc('abc').get().then(doc => {
console.log(doc.data());
return response.send(doc.data());
}).catch(error => {
return response.status(500).send(error);
});
});
Important: Don't forget to terminate the request by calling response.redirect(), response.send(), or responses.end() so you can avoid excessive charges from functions that run for too long
We're trying to write a Google Cloud Function that gets a translation from Google Translate API, and then write the results to our Firebase Firestore database. Each works alone, but together nothing works. In other words, we can get a translation from Google Translate. We can write data to Firestore. But if we try to do both, we don't get a translation back from Google Translate, and nothing is written to Firebase. We get no error messages. We've tried the code with async await and with promises. Here's the code with promises:
exports.Google_EStranslateEN = functions.firestore.document('Users/{userID}/Spanish/Translation_Request').onUpdate((change, context) => {
if (change.after.data().word != undefined) {
const {Translate} = require('#google-cloud/translate');
const projectId = 'myProject-cd99d';
const translate = new Translate({
projectId: projectId,
});
// The text to translate
const text = change.after.data().word;
// The target language
const target = 'en';
let translationArray = []; // clear translation array
translate.translate(text, target)
.then(results => {
translation = results[0];
translationArray.push(translation);
try {
// write translation to dictionary
admin.firestore().collection('Dictionaries').doc('Spanish').collection('Words').doc(text).collection('Translations').doc('English').set({
'translationArray': translationArray,
'language': 'en',
'longLanguage': 'English'
})
.then(function() {
console.log("Translation written");
})
.catch(function(error) {
console.error(error);
});
} catch (error) {
console.error(error);
}
})
.catch(error => {
console.error('ERROR:', error);
});
}
});
Here's the same code with async await:
exports.Google_EStranslateEN = functions.firestore.document('Users/{userID}/Spanish/Translation_Request').onUpdate((change, context) => { // triggers when browser writes a request word to the database
if (change.after.data().word != undefined) {
async function getTranslation() {
try {
let translationArray = []; // clear translation array
const {Translate} = require('#google-cloud/translate');
const projectId = 'myProject-cd99d';
const translate = new Translate({
projectId: projectId,
});
// The text to translate
const text = change.after.data().word;
const options = {
to: 'en',
from: 'es',
format: 'text'
}
let [translations] = await translate.translate(text, options);
translations = Array.isArray(translations) ? translations : [translations]; // If translations is an array, leave it alone; if not, put it in an array
translationArray.push(translations[0]);
await admin.firestore().collection('Dictionaries').doc('Spanish').collection('Words').doc(text).collection('Translations').doc('English').set({
'translationArray': translationArray,
'language': 'en',
'longLanguage': 'English'
})
.then(function() {
console.log("Translation written");
})
.catch(function(error) {
console.error(error);
});
// };
} catch (error) {
console.error(error);
}
} // close getTranslation
getTranslation();
}
});
You're not returning a promise that's resolved when all the async work is complete. If you don't do that, Cloud Functions assumes that all your work is complete, and will clamp down on all resources, and any pending work will be shut down.
The promise returned by translate.translate().then().catch() is being ignored. Your nested call to admin.firestore()...set() has a similar problem. It's not sufficient to just call then() and catch() on every promise because then() and catch() both return yet another promise.
You're also unnecessarily mixing usage of try/catch with catch() on the promise. You don't need both strategies for error handling, just one or the other.
When you used await in your second example, you forced JavaScript to pause until the async work represented by the promise returned by set() was complete. This allowed your function to return only after all the work was finished, which is why it worked correctly.
You might be helped by watching my video series on use of promises and async/await in Cloud Functions. Proper handling of promises is crucial to creating a correctly working function.