Amazon Alexa Skill Lambda Node JS - Http GET not working

1.6k Views Asked by At

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.

2

There are 2 best solutions below

2
On BEST ANSWER

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();
}
2
On

Node.js is asynchronous by default, which means that your response builder is called before the GET request is completed.

Solution: Use async-await , Something like this

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"
        }
      };

      var req = await 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();
  },

Let me know if this doesn't work. Also to develop alexa skills for personal use, checkout alexa blueprints.