Alexa-skills-kit-sdk-for-nodejs: Asynchronous callback not firing this.emit(':ask') before exit of HandleLambdaEvent()

Created on 24 Oct 2016  路  7Comments  路  Source: alexa/alexa-skills-kit-sdk-for-nodejs

I have an event handler which is making an external call to a REST interface. The event handler is setup to perform the this.emit(':ask', ...) in the callback from a request call to the remote service. The service takes about 1-2 seconds to return data and is asynchronous (from the npm request package)

var request = require('request);
function myHandler(input, callback) {
         request('http://rest_url...', function(error, response, body) { 
               if (response) { // we have a success so call the call back
                     callback(null, 'found it!');
               } else {
                     callback(error);
               }
        }
}

The callback, which is called from the event handler, then performs the this.emit(':ask'),

'myEventHandler': function() {
        myHandler('findThisData', (error, rsp) => {
            if(error) {
                this.emit(":tell",  "I'm sorry, but I can't find  that information.");
            } else {
               this.emit(":ask", " I found!", "Want to do anything else?");
        });
       // notice no emit or context.success/fail/done at end of event handler
}

The alexa-sdk seems to call HandleLambdaEvent() which calls EmitEvent which will return from myEventHandler before the callback is executed. The delay return in the asynchronous callback will get executed after HandleLambdaEvent() has returned. This sequence seems to return a 'null' to the lambda function and fails to ever get a chance to handle the eventual call to this.emit() processed in the callback function.

Most helpful comment

Thanks @jerolimov - this will work just fine

If arrow functions aren't your thing, another popular option is to keep a pointer to the real 'this' context at the top of the function. I've made it a practice in cases where I have callback functions in case I forget to use an arrow function.

function myHandler(input, callback) {
         const self = this;  // <--- pointer to this in the outer function scope
         request('http://rest_url...', function(error, response, body) {
               if (response) { // we have a success so call the call back
                     self.emit(":tell", "hello"); //  <--- self points to the object you want
               } else {
                     self.emit(':ask', 'hello');
               }
        }
}

closing this as resolved. Feel free to re-open if you have further questions

All 7 comments

Have you tried creating a variable outside of the function?

Ex. var self = this;

self.tell(':tell','found it!');

I did not explicitly create a new variable, but I did verify that the correct _this_ was bound at the execution. Since the fat arrow is used, _this_ should be bound correctly. Over the weekend, I may see if an explicit Promise resolves the issue. I was able to work around it by using the request_sync library.

I have the same issue. Were you able to resolve it?

The keyword this is bounded to the last function scope, which is in your case the request callback block.

Instead of writing function (error, response, body) { .. } you should write (error, response, body) => { ... }. This does not re-bind the this-scope, so that this is not changed in the callback.

Hope this helps you @svonkleeck and @JasonVonKrueger.

Complete fix for the original issue on top:

var request = require('request);
function myHandler(input, callback) {
         request('http://rest_url...', (error, response, body) => {
               if (response) { // we have a success so call the call back
                     callback(null, 'found it!');
               } else {
                     callback(error);
               }
        }
}

Thanks @jerolimov - this will work just fine

If arrow functions aren't your thing, another popular option is to keep a pointer to the real 'this' context at the top of the function. I've made it a practice in cases where I have callback functions in case I forget to use an arrow function.

function myHandler(input, callback) {
         const self = this;  // <--- pointer to this in the outer function scope
         request('http://rest_url...', function(error, response, body) {
               if (response) { // we have a success so call the call back
                     self.emit(":tell", "hello"); //  <--- self points to the object you want
               } else {
                     self.emit(':ask', 'hello');
               }
        }
}

closing this as resolved. Feel free to re-open if you have further questions

I have been trying to call the ticketmaster API in an alexa skill I am building using the SDK and am having a lot of trouble accessing the JSON response. I tried to use the format that you have outlined, but I clearly do not know how to access the right part of the response. In my function, I am doing self.emit and emitting the list of Object keys for "response", which does not seem to be the JSON response. Body, I have discovered, is of type string, also not the JSON response. I would appreciate any advice you could give me. @bclement-amazon @jerolimov

'showsNearMe': function (input,callback) {

    var filledSlots = delegateSlotCollection.call(this);

    //compose speechOutput that simply reads all the collected slot values
  var speechOutput = randomPhrase(showIntro);


  var showCity=this.event.request.intent.slots.showCity.value;
  var timePeriod=this.event.request.intent.slots.timePeriod.value + 'T10:00:00Z';


  const self = this;  // <--- pointer to this in the outer function scope
  request("https://app.ticketmaster.com/discovery/v2/events.json?city=Boston&size=1&segmentName=Music&apikey=bP5gQH4GDftg8tE8j3dyXHYYPdNzDBV7", function(error, response, body) {
           if (response) { 
                self.emit(":tell",Object.keys(response).join(' '));
           } else {
                 self.emit(':ask', 'hello');
           }
    })

},

Hello together,

I have also the problem the when asking Alexa for something, there is always this NoSpeechlet response when invoking the searchIntent. I have been struggling for two days without a solution. Here is the code:

'use strict';

var Alexa = require('alexa-sdk');
var http = require('http');
var axios = require('axios');

var calendar = require('./helpers/googleCalendar.js');
var self = this;

// Register handlers and initialize start
exports.handler = function(event, context, callback) {
  var alexa = Alexa.handler(event, context);
  alexa.appId = APP_ID;
  alexa.registerHandlers(newSessionHandlers, startSearchHandlers);
  console.log('before exec');
  alexa.execute();
  console.log('after exec');
};

// Reference to Alexa object

// States
const states = {
  SEARCHMODE: '_SEARCHMODE'
};
// Messages and other reoccuring data
const APP_ID = 'amzn1.ask.skill.e0cd726d-f53c-4769-967c-8cacd8862a80';
const helpMessage =
  'For example, you can ask: What are my appointments for today?';
const HELP_REPROMPT = 'What can I help you with?';
const STOP_MESSAGE = 'Goodbye!';
const SKILL_NAME = 'Miracle';
const welcomeMessage = 'You can ask for your todays appointments';
const shutdownMessage = 'Ok. See you soon.';
const errorMessage = 'Ups... Something went wrong. Please start me again.';

// Adding session handlers
const newSessionHandlers = {
  NewSession: function() {
    this.handler.state = states.SEARCHMODE;
    console.log('I am in the launch');
    this.response
      .speak(`Welcome to ${SKILL_NAME}` + ' ' + ' ' + welcomeMessage)
      .listen(welcomeMessage);
    this.emit(':responseReady');
  },
  Unhandled: function() {
    this.response.speak(helpMessage).listen(helpMessage);
    this.emit(':responseReady');
  }
};

// Creating hanlders for state SEARCH state
const startSearchHandlers = Alexa.CreateStateHandler(states.SEARCHMODE, {
  // Built-in intents
  'AMAZON.YesIntent': function() {
    output = welcomeMessage;
    this.response.speak(output).listen(welcomeMessage);
    this.emit(':responseReady');
  },
  'AMAZON.NoIntent': function() {
    this.response.speak(shutdownMessage);
    this.emit(':responseReady');
  },

  // 'AMAZON.RepeatIntent': function() {
  //   this.response.speak(output).listen(HelpMessage);
  // },
  SessionEndedRequest: function() {
    this.emit('AMAZON.StopIntent');
  },

  searchIntent: function() {
    const url = 'http://127.0.0.1:5000/customers';

    axios.get(url).then(
      response => {
        console.log('Success!', response); // Yea, REST all the things
        self.emit(':tell', 'ok');
      },
      error => {
        console.error('Failure!', response); // Boo, bad feels
        self.emit(
          ':tell',
          "Hmm.. that didn't work.  Check the CloudWatch Luke."
        );
      }
    );
  }
});

I really appreciate your help.

Best regards
Oli

Was this page helpful?
0 / 5 - 0 ratings

Related issues

dillonharlessNHRMC picture dillonharlessNHRMC  路  6Comments

kevinwu picture kevinwu  路  3Comments

talkingnews picture talkingnews  路  5Comments

developer170883 picture developer170883  路  4Comments

slesem picture slesem  路  4Comments