Search code examples
node.jsasync-awaitdialogflow-esactions-on-googleairtable

Dialogflow - Reading from database using async/await


it's the first time for me using async/await. I've got problems to use it in the context of a database request inside a dialogflow intent. How can I fix my code?

What happens?

When I try to run use my backend - this is what I get: "Webhook call failed. Error: Request timeout."

What do I suspect?

My helper function getTextResponse() waits for a return value of airtable, but never get's one.

What do I want to do?

  1. "GetDatabaseField-Intent" gets triggered
  2. Inside it sends a request to my airtable database via getTextResponse()
  3. Because I use"await" the function will wait for the result before continuing
  4. getTextResponse() will return the "returnData"; so the var result will be filled with "returnData"
  5. getTextResponse() has finished; so the response will be created with it's return value
'use strict';

const {
  dialogflow
} = require('actions-on-google');

const functions = require('firebase-functions');
const app = dialogflow({debug: true});
const Airtable = require('airtable');
const base = new Airtable({apiKey: 'MyKey'}).base('MyBaseID');

///////////////////////////////
/// Helper function - reading Airtable fields.
const getTextResponse = (mySheet, myRecord) => {
    return new Promise((resolve, reject) => {
        // Function for airtable
        base(mySheet).find(myRecord, (err, returnData) => {
        if (err) {
            console.error(err);
            return; 
        }
        return returnData;
        });
    }       
)};

// Handle the Dialogflow intent.
app.intent('GetDatabaseField-Intent', async (conv) => {
  const sheetTrans = "NameOfSheet";
  const recordFirst = "ID_OF_RECORD";

  var result = await getTextResponse(sheetTrans, recordFirst, (callback) => {
    // parse the record => here in the callback
    myResponse = callback.fields.en;

  });
  conv.ask(myResponse);
});

// Set the DialogflowApp object to handle the HTTPS POST request.
exports.dialogflowFirebaseFulfillment = functions.https.onRequest(app);

Solution

  • As @Kolban pointed out, you are not accepting or rejecting the Promise you create in getTextResponse().

    It also looks like the var result = await getTextResponse(...) call is incorrect. You have defined getTextResponse() to accept two parameters, but you are passing it three (the first two, plus an anonymous arrow function). But this extra function is never used/referenced.

    I would generally avoid mixing explicit promises with async/await and definitely avoid mixing async/await with passing callbacks.

    I don't know the details of the API you are using, but if the API already supports promises, then you should be able to do something like this:

    const getTextResponse = async (mySheet, myRecord) => {
        try {
            return await base(mySheet).find(myRecord)
        }
        catch(err) {
            console.error(err);
            return; 
        }
    )};
    
    ...
    
    app.intent('GetDatabaseField-Intent', async (conv) => {
      const sheetTrans = "NameOfSheet";
      const recordFirst = "ID_OF_RECORD";
    
      var result = await getTextResponse(sheetTrans, recordFirst)
      myResponse = result.fields.en;
      conv.ask(myResponse);
    });
    
    ...
    

    Almost all promised based libraries or APIs can be used with async/await, as they simply use Promises under the hood. Everything after the await becomes a callback that is called when the awaitted method resolves successfully. Any unsuccessful resolution throws a PromiseRejected error, which you handle by use of a try/catch block.