1

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.

johndoe
  • 4,387
  • 2
  • 25
  • 40
Chris Wakefield
  • 81
  • 1
  • 1
  • 9

2 Answers2

2

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.

Ussama Zubair
  • 1,039
  • 1
  • 13
  • 19
  • I tried the above code and it's working perfectly. This is all new to me as I've only just started with node js. I will look into the blueprints too as suggested. Thanks so much for your help, this has been troubling me for about a week now! Note to self, read up on async! – Chris Wakefield Oct 13 '18 at 10:27
  • Sorry, on reflection this isn't actually working. I thought it was, but it's actually doing the same thing as before, with undefined being returned first. I was confused as it always works the second time, presumably because the variable gets set after it's read out by Alexa – Chris Wakefield Oct 13 '18 at 11:31
1

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();
}
johndoe
  • 4,387
  • 2
  • 25
  • 40
  • Thanks so much for your help, I'm just starting to get my head around asynchronicity so this is aassive help. I'll try asyouve mentioned above by picking apart the example for my uses and post back. Thanks so much for the help. – Chris Wakefield Oct 13 '18 at 14:57
  • Still not sure what's going on here, but I'm still getting undefined even with the above code changes. I've placed the request into it's own function as recommended above and called it also as above but still no dice. Only on the second request I get a value. EDIT: I had made a mistake, the above works perfectly, thanks so much for the help guys, this is exactly what I needed, and now know a lot more about async than I did when I woke this morning. Thanks again. – Chris Wakefield Oct 13 '18 at 16:54