Search code examples
javascriptgetalexa-skills-kitalexa-sdk-nodejs

Amazon Alexa Skill Lambda Node JS - Http GET not working


I'm wondering if someone can help as I'm beating my head against a wall with this. I've been looking for answers for days and tried various things and below is the closest I've got.

Basically I'm building an Alexa skill for personal use in my home to give reward points to my son, and it updates on our kitchen dashboard. I can POST points fine and the dashboard updated (updates a firebase db) but I can't GET the points when I ask alexa how many he has. My code is below.

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

      var options = {
        "method": "GET",
        "hostname": "blah-blah-blah.firebaseio.com",
        "port": null,
        "path": "/users/Connor/points.json",
        "headers": {
          "cache-control": "no-cache"
        }
      };

      var req = https.request(options, function (res) {
        var chunks = [];

        res.on("data", function (chunk) {
          chunks.push(chunk);
        });

        res.on("end", function () {
          var body = Buffer.concat(chunks);
          //console.log(body.toString());
          total = body.toString();

        });
    });

    req.end();

    speechOutput = "Connor has " + total + " points";

    return handlerInput.responseBuilder
      .speak(speechOutput)
      .getResponse();
  },
};

The result at the moment when I ask alexa is "Connor has undefined points", but then if I ask again immediately, it works fine.

The json endpoint literally just shows the value when I load it up in a browser, so don't need to dig into the response I don't think.

I know that the request module is supposed to be easier, but if I install that using VS code command line and upload the function, because the file becomes so big with all the module dependencies, I can no longer edit the function online as it is over the size limit, so want to avoid this if possible.

I know that the function would be better placed as a helper which I will do once I get this working. I don't need this to be particularly pretty, just need it to work.


Solution

  • This is because of the asynchronos behavior of nodejs. Node won't wait for your http request to complete. So the speechOutput = "Connor has " + total + " points"; is executed even before the value of total is fetched. Hence, undefined.

    To make this work you have to use Promises. Write a separate function to fire http requests. Check this PetMatchSkill and see how it's done. You can use this as a common method for any requests.

    Ex:

    function httpGet(options) {
      return new Promise(((resolve, reject) => {
        const request = https.request(options, (response) => {
          response.setEncoding('utf8');
          let returnData = '';    
          if (response.statusCode < 200 || response.statusCode >= 300) {
            return reject(new Error(`${response.statusCode}: ${response.req.getHeader('host')} ${response.req.path}`));
          }    
          response.on('data', (chunk) => {
            returnData += chunk;
          });    
          response.on('end', () => {
            resolve(JSON.parse(returnData));
          });    
          response.on('error', (error) => {
            reject(error);
          });
        });
        request.end();
      }));
    }
    

    Now in your intent handler use async-await.

    async handle(handlerInput) {
       var options = {
            "method": "GET",
            "hostname": "blah-blah-blah.firebaseio.com",
            "port": null,
            "path": "/users/Connor/points.json",
            "headers": {
              "cache-control": "no-cache"
            }
          };
       const response = await httpGet(options);
       var total = 0;
       if (response.length > 0) {
          // do your stuff
          //total = response
       }
       speechOutput = "Connor has " + total + " points";
       return handlerInput.responseBuilder
         .speak(speechOutput)
         .getResponse();
    }