How to make an asynchronous api call for Alexa Skill application with a Lambda function?

自闭症网瘾萝莉.ら 提交于 2019-12-24 08:00:07

问题


I want to call an api from a Lambda function. My handler is triggered by an intent which includes two required slots. Therefore I don't know in advance whether I will be returning a Dialog.Delegate directive or my response from the api request. How do I promise these return values by the time the intent handler is called?

This is my handler:

const FlightDelayIntentHandler = {
  canHandle(handlerInput) {
    return handlerInput.requestEnvelope.request.type === 'IntentRequest'
      && handlerInput.requestEnvelope.request.intent.name === 'MyIntent';
  },

  handle(handlerInput) {
    const request = handlerInput.requestEnvelope.request;

    if (request.dialogState != "COMPLETED"){
      return handlerInput.responseBuilder
        .addDelegateDirective(request.intent)
        .getResponse();
    } else {
      // Make asynchronous api call, wait for the response and return.
      var query = 'someTestStringFromASlot';

      httpGet(query,  (result) => {
        return handlerInput.responseBuilder
          .speak('I found something' + result)
          .reprompt('test')
          .withSimpleCard('Hello World', 'test')
          .getResponse();
      });
    }
  },
};

This is my helper function which makes the request:

const https = require('https');

function httpGet(query, callback) {
    var options = {
        host: 'myHost',
        path: 'someTestPath/' + query,
        method: 'GET',
        headers: {
            'theId': 'myId'
        }
    };

    var req = https.request(options, res => {
        res.setEncoding('utf8');
        var responseString = "";

        //accept incoming data asynchronously
        res.on('data', chunk => {
            responseString = responseString + chunk;
        });

        //return the data when streaming is complete
        res.on('end', () => {
            console.log('==> Answering: ');
            callback(responseString);
        });

    });
    req.end();
}

So I suspect I will have to use promises and put an "async" in front of my handle function? I am very new to all of this so I don't know the implications of this, especially considering the fact that I have two different return values, one directly and the other one delayed. How would I solve this?

Thank you in advance.


回答1:


As you suspected, your handler code is finishing before the asynchronous call to http.request, hence the Alexa SDK receives no return value from the handle function and will return an invalid response to Alexa.

I slightly modified your code to run it locally on a laptop to illustrate the issue :

const https = require('https');

function httpGet(query, callback) {
    var options = {
        host: 'httpbin.org',
        path: 'anything/' + query,
        method: 'GET',
        headers: {
            'theId': 'myId'
        }
    };

    var req = https.request(options, res => {
        res.setEncoding('utf8');
        var responseString = "";

        //accept incoming data asynchronously
        res.on('data', chunk => {
            responseString = responseString + chunk;
        });

        //return the data when streaming is complete
        res.on('end', () => {
            console.log('==> Answering: ');
            callback(responseString);
        });

    });
    req.end();
}

function FlightDelayIntentHandler() {

    // canHandle(handlerInput) {
    //   return handlerInput.requestEnvelope.request.type === 'IntentRequest'
    //     && handlerInput.requestEnvelope.request.intent.name === 'MyIntent';
    // },

    // handle(handlerInput) {
    //   const request = handlerInput.requestEnvelope.request;

    // if (request.dialogState != "COMPLETED"){
    //     return handlerInput.responseBuilder
    //       .addDelegateDirective(request.intent)
    //       .getResponse();
    //   } else {
        // Make asynchronous api call, wait for the response and return.
        var query = 'someTestStringFromASlot';

        httpGet(query,  (result) => {
            console.log("I found something " + result);

        //   return handlerInput.responseBuilder
        //     .speak('I found something' + result)
        //     .reprompt('test')
        //     .withSimpleCard('Hello World', 'test')
        //     .getResponse();
        });

        console.log("end of function reached before httpGet will return");
    //   }
    // }
}

FlightDelayIntentHandler();

To run this code, do not forget npm install http , then node test.js. It produces

stormacq:~/Desktop/temp $ node test.js
end of function reached before httpGet will return
==> Answering:
I found something {
  "args": {},
  "data": "",
... 

So, the key is to wait for http get to return before to return a response to Alexa. For that, I propose to modify your httpGet function to return a promise instead using callbacks.

Modified code is like this (I kept your original code as comment)

const https = require('https');

async function httpGet(query) {
    return new Promise( (resolve, reject) => {
        var options = {
            host: 'httpbin.org',
            path: 'anything/' + query,
            method: 'GET',
            headers: {
                'theId': 'myId'
            }
        };

        var req = https.request(options, res => {
            res.setEncoding('utf8');
            var responseString = "";

            //accept incoming data asynchronously
            res.on('data', chunk => {
                responseString = responseString + chunk;
            });

            //return the data when streaming is complete
            res.on('end', () => {
                console.log('==> Answering: ');
                resolve(responseString);
            });

            //should handle errors as well and call reject()!
        });
        req.end();

    });

}



async function FlightDelayIntentHandler() {

        // canHandle(handlerInput) {
    //   return handlerInput.requestEnvelope.request.type === 'IntentRequest'
    //     && handlerInput.requestEnvelope.request.intent.name === 'MyIntent';
    // },

    // handle(handlerInput) {
    //   const request = handlerInput.requestEnvelope.request;

    // if (request.dialogState != "COMPLETED"){
    //     return handlerInput.responseBuilder
    //       .addDelegateDirective(request.intent)
    //       .getResponse();
    //   } else {
        // Make asynchronous api call, wait for the response and return.
        var query = 'someTestStringFromASlot';

        var result = await httpGet(query);
        console.log("I found something " + result);

        //   return handlerInput.responseBuilder
        //     .speak('I found something' + result)
        //     .reprompt('test')
        //     .withSimpleCard('Hello World', 'test')
        //     .getResponse();
        //});

        console.log("end of function reached AFTER httpGet will return");
    //   }
    // }
}

FlightDelayIntentHandler();

Running this code produces :

stormacq:~/Desktop/temp $ node test.js
==> Answering:
I found something{
  "args": {},
  "data": "",
...
end of function reached AFTER httpGet will return


来源:https://stackoverflow.com/questions/51764274/how-to-make-an-asynchronous-api-call-for-alexa-skill-application-with-a-lambda-f

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!