dialogflow-esgoogle-homefn

Triggering the fulfillment webhook asynchronously from an intent?


I have some intents that need to trigger the fulfillment webhook and don't care about the response. The webhook takes longer than the timeout to respond so I'd like the intent to simply respond with "Thanks for chatting" and then close the conversation while actually triggering the webhook.

Feels easy but I'm missing something. Also I'm new to the dialogflow stuff.

I can do this in any language, but here's an example in Javascript:

fdk.handle(function (input) {
  // Some code here that takes 20 seconds.

  return {'fulfillmentText': 'i can respond but I will never make it here.'}
});

EDIT 1 - Trying async

When I use an async function, the POST request never happens. So in the following code:

fdk.handle(function (input) {
  callFlow(input);
  return { 'fulfillmentText': 'here is the response from the webhook!!' }
});

async function callFlow(input) {
  console.log("input is --> " + input)

  var url = "some_url"

  console.log("Requesting " + url)

  request(url, { json: true, headers: {'Access-Control-Allow-Origin' : '*'} }, (err, res, body) => {
    if (err) { return console.log(err); }
    console.log("body is...")
    console.log(body)
  });
}

I see in the logs the two console.log outputs but nothing from the request. And the request doesn't seem to happen either because I don't see it at my endpoint.

SOLUTION

Thanks Prisoner for the tip. Seems like I needed to return the fulfillment JSON back through the callFlow() and handle() functions. Now Google Home doesn't timeout and both the HTTP call and response are generated.

const fdk = require('@fnproject/fdk');
const request = require('request');

fdk.handle(function (input) {
  return callFlow(input);
});

async function callFlow(input) {
  var searchPhrase = input || "cats"
  var url = "some url"

  return new Promise((resolve, reject) => {
    request.post(url, {
      headers: { 'content-type': 'application/x-www-form-urlencoded' },
      body: searchPhrase
    },
      function (err, resp, body) {
        if (err) { return console.log(err) }
        r = { 'fulfillmentText': `OK I've triggered the flow function with search term ${searchPhrase}` }
        resolve(r)
      }
    );
  });

}

Solution

  • You cannot trigger the fulfillment asynchronously. In a conversational model, it is expected that the fulfillment will perform some logic that determines the response.

    You can, however, perform an asynchronous operation in the fulfillment that does not complete before you return the result.

    If you are using a sufficiently modern version of node (version 8 and up), you can do this by declaring a function as an async function, but not calling it with the await keyword. (If you did call it with await, it would wait for the asynchronous operation to complete before continuing.)

    So something like this should work, given your example:

    async function doSomethingLong(){
      // This takes 20 seconds
    }
    
    fdk.handle(function (input) {
      doSomethingLong();
    
      return {'fulfillmentText': 'This might respond before doSomethingLong finishes.'}
    });
    

    Update 1 based on your code example.

    It seems odd that you report that the call to request doesn't appear to be done at all, but there are some odd things about it that may be causing it.

    First, request itself isn't an async function. It is using a callback model and async functions don't just automatically wait for those callbacks to be called. So your callFlow() function calls console.log() a couple of times, calls request() and returns before the callbacks are called back.

    You probably should replace request with something like the request-promise-native package and await the Promise that you get from the call. This makes callFlow() truly asynchronous (and you can log when it finishes the call).

    Second, I'd point out that the code you showed doesn't do a POST operation. It does a GET by default. If you, or the API you're calling, expect a POST, that may be the source of the error. However, I would have expected the err parameter to be populated, and your code does look like it checks for, and logs, this.

    The one unknown in the whole setup, for me, is that I don't know how fdk handles async functions, and my cursory reading of the documentation hasn't educated me. I've done this with other frameworks, and this isn't a problem, but I don't know if the fdk handler times out or does other things to kill a call once it sends a reply.