2

I am using Dialogflow to build an Action for Google Assistant. Everything works, except the Fulfillment of my intent.

I am using the Inline Editor (Powered by Cloud Functions for Firebase) to fulfill the intent. My function in itself runs - since I can send text to the assistant from the function.

But for some reason, code execution never enters the the function that fetches data from my Collection on Firebase Firestore - although it does execute commands before and after.

Here is the code in my index.js.

'use strict';
const admin = require('firebase-admin');
const functions = require('firebase-functions');
admin.initializeApp(functions.config().firebase);
let db = admin.firestore();
const {dialogflow} = require('actions-on-google');
const app = dialogflow({debug: true});

app.intent('INTENT', (conv, {ENTITY}) => {
    conv.add("Hello.");         //THIS IS DISPLAYED

    db.collection("COLLECTION").orderBy("FIELD", "desc").get().then(snapshot => {
        conv.add("Hey!");       //THIS IS NOT DISPLAYED
    snapshot.forEach(doc => {
        conv.add("Hi?");        //NOR IS THIS       
    });
        conv.add("Hmm...");     //NEITHER THIS
    }).catch(error => {
        conv.add('Error!');     //NOT EVEN THIS
    });

    conv.add("Bye.");           //THIS IS DISPLAYED TOO
});

exports.dialogflowFirebaseFulfillment = functions.https.onRequest(app);

Clearly, this shows that execution never really entered the db... block, and hence the function didn't even throw any error.

Here are the logs from Firebase.

Function execution started
Billing account not configured...
Warning, estimating Firebase Config based on GCLOUD_PROJECT. Initializing firebase-admin may fail
Request {...}
Headers {...}
Conversation {...}
Response {...}
Function execution took 1681 ms, finished with status code: 200

I know that the firestore function fetches data asynchronously, but there seems no way I could execute anything even inside its .then(...) block.

I also tried returning a Promise from the .then(...) block, and using a second .then(...) with it - which again didn't work.

var fetch = db.collection("COLLECTION").orderBy("FIELD", "desc").get().then(snapshot => {
    conv.add("Hey!");                    //NOT DISPLAYED
    var responseArr = [];
    snapshot.forEach(doc => {
        conv.add("Hi?");                 //NOT DISPLAYED  
        responseArr.push(doc);  
    });
    conv.add("Hmm...");                  //NOT DISPLAYED
    return Promise.resolve(responseArr);
}).then(fetch => {
    conv.add("Here?");                   //NOT DISPLAYED
}).catch(error => {
    conv.add('Error!');                  //NOT DISPLAYED
});

Finally, I also tried putting the firestore function in a separate function, like this.

function getData(){
    return db.collection("COLLECTION").orderBy("FIELD", "desc").get().then(snapshot => {
        snapshot.forEach(doc => {
            ...    
        });

        return data;                //Manipulated from above. 'data' can be a string.
    }).catch(error => {
           return error;
    });
}

app.intent('INTENT', (conv, {ENTITY}) => {
    conv.add("Hello.");         //THIS IS DISPLAYED

    conv.add(getData());        //THIS IS NOT DISPLAYED

    conv.add("Bye.");           //THIS IS DISPLAYED
});
Mrigank Pawagi
  • 892
  • 1
  • 8
  • 26
  • 1
    As it said - if the other answer doesn't address your question, go ahead and [ask a new one](https://stackoverflow.com/questions/ask). Editing the question isn't enough, but if the other answer doesn't help - go ahead and re-ask, clarifying what part of the other answer didn't make sense. – Prisoner Jun 27 '19 at 16:03
  • @Prisoner Sure, thanks! It seems you answered the *original* question - it would be great if you could help. – Mrigank Pawagi Jun 27 '19 at 16:15
  • 1
    What about it doesn't answer your question? – Prisoner Jun 27 '19 at 16:16
  • @Prisoner I read a couple answers more, which were also coincidentally written by you. With that reference, I `return`ed a `Promise` in the `.then(...)` block, and added a second `.then(...)` block - yet there's no difference. – Mrigank Pawagi Jun 27 '19 at 16:29
  • Re-post the question with what you've tried and I'll provide a longer answer. – Prisoner Jun 27 '19 at 17:03
  • @Prisoner I have updated the answer. Kindly tell me if there's any thing I need to provide. – Mrigank Pawagi Jun 28 '19 at 11:23
  • @Prisoner I believe, since you have the [gold] `actions-on-google` badge, you can single handedly re-open the question. Reposting would duplicate my own question. – Mrigank Pawagi Jun 28 '19 at 13:27

1 Answers1

2

The problem is that you're doing an asynchronous operation (the call to get()), but you're not returning a Promise from the Intent Handler itself. The library requires you to return a Promise so it knows that there is an async operation taking place.

Returning a Promise from inside the then() portion isn't enough - that doesn't return a value from the handler, it just returns a value that is passed to the next then() function or (if it was the last one) as the return value of the entire Promise chain.

In your original code, this can be done just by returning the get().then().catch() chain. Something like this as your first line:

return db.collection("COLLECTION").orderBy("FIELD", "desc").get() // etc etc

In your second example, the fetch in your then() block is not the fetch that you think it is, and only confuses matters. Structured that way, you need to return the fetch from the let assignment.

Your third example is more complicated. The line

conv.add(getData());

doesn't even seem like it would work, on the surface, because it is returning a Promise, but you can't add a promise to the conv object. You would need to rewrite that part as

return getData()
  .then( data => conv.add( data ) );

But that doesn't address how the "Bye" line would work. If you actually wanted "Bye" after the data, you would have to include it as part of the then() block.

In short, when dealing with async data, you need to

  1. Make sure you understand how Promises work and make sure all async work is done using Promises.
  2. Add all your data inside the then() portion of a Promise
  3. Return a Promise correctly
Prisoner
  • 49,922
  • 7
  • 53
  • 105
  • Thank you so much! `return getData().then( data => conv.add( data ) );` worked for me so well! Promises are really a nightmare for me... – Mrigank Pawagi Jun 28 '19 at 16:14