I'm calling out to an external REST API with the https module, and even wrapping the call in a Promise, a null response is returned by default even if .emit() is not called.
I'm assuming that .execute() calls HandleLambdaEvent() when any handler returns, ending the lambda context. The async code completes a few seconds later, and in the .then() of its promise calls .emit(), but the response has already returned. Am I missing something in the source code that allows a handler to suppress auto response send and manually .emit() when async returns?
This is a really handy module and I'm probably just missing something obvious in my code:
const Alexa = require('alexa-sdk');
const https = require('https');
const handlers = {
'GoGetRESTfoo': function () {
console.log("GoGetRESTfoo called");
promisedRESTrequest('Some foo bar url').then(function(response) {
console.log("Success!", response); // Yea, REST all the things
this.emit(':tell', 'ok');
}, function(error) {
console.error("Failure!", response); // Boo, bad feels
this.emit(':tell', 'Hmm.. that didn\'t work. Check the CloudWatch Luke.');
});
},
// lots more handlers..
}
exports.handler = (event, context) => {
const alexa = Alexa.handler(event, context);
alexa.APP_ID = APP_ID;
alexa.registerHandlers(handlers);
console.log('before exec');
alexa.execute();
console.log('after exec');
};
Thanks!
I got it to work using async functions. Here is my code
import * as Alexa from 'alexa-sdk'
const handlers = {
GiveNoteAsync() {
setTimeout(() => {
this.emit(':tell', 'Hello Async')
}, 500)
},
GiveNoteSync() {
this.emit(':tell', "Hello Sync")
)
}
export function handler(event, context, callback) {
const alexa = Alexa.handler(event, context)
alexa.registerHandlers(handlers)
alexa.execute()
}
I tested this and it worked just fine. Hope this helps.
@JeremyBYU - Thank you! Works like a charm.
Are there any alternatives to doing it this way (using setTimeout)? I can't seem to get it to work for whatever reason anyhow!
@runtimeware : I also got stuck on this issue. You dont really need the setTimeout.
As long as the correct context for emit is maintained (either using another variable to capture this, or es 6 fat arrow) in the callback of the async flow, it works.
The caveat is by default, the ~maximum~ default time for any lambda function is 1 sec. And if it takes longer than that, an invalid response is automatically returned and this is not logged. Try increasing it, based on your usecase
The setTimeout was only there to demonstrate that asynchronous functions do work inside this package (alexa-skil-kit-sdk). You can use any other async function that you need.
As for the maximum time of a lambda function, I'm pretty sure that its actually 5 minutes. Link
As for how long the Alexa service will wait for a response from you, that I'm not sure about.
I think there is a bug somewhere, because I am also getting some very mixed responses. Stuff that works, only works 1/3 or at best 2/4 times.
I looked up setting a proper return, but that also fails on some stuff.
If anyone has a complete real example (abstract the API/REST call as a function) -- that would be great.
I also think there is a bug in the SDK somewhere btw, because if you take the sample stuff "as is" and add your REST calls, it produces an issue in regards to the return/timeout. (even when the APIs take 1-2 seconds)
Im doing asynchronous DB calls and it's working everytime for me. But the max time it's been taking for DB (dynamodb) calls is less than a sec, so I can't speak for timeout issues occuring on long asynchronous calls.
I am having the same issue. Asynchronous actions with callback works, however when using Promises, it does not work. My 'then' is never called and when I test using a 'reject' I am getting an error about an unhandled promise rejection (even though I have a valid catch there).
How do we move forward with this? Should I create a new issue for this since the title of this does not specifically mention promises and async callbacks actually work?
I would really like doing alexa skills without callback hell using promises.
FYI if you create a then function as displayed in the original post, probably the "this.emit" will not work because "this" points to the wrong thing.
However if you create a then function as displayed in the timeout solution, it should work because it preserves "this". So you may need your code to look like this:
somePromise.then(
(result) => {... this.emit ...},
(error) => {... this.emit ...}
);
@schaze - I agree -- this is frustrating. If you haven't already, please open a separate issue. I am also running into this with every skillset that has an API call. Even when successfully returning/emit-ing to properly "close" the connection with Alexa, it does the right thing in some case (and others it does not because it times out), and then still fails.
Also - it would be great to get a _working_ example of something with an API call.
So, for some reason it now started to work for me using Promises. To be honest I do not really know why or what I did (or what I missed before).
Here is an example of a simple Intent that will start my TV through my FHEM server via a Logitech Harmony Hub (it uses a PHP REST API I put in front of FHEM called bridgeAPI).
'activityFernsehen': function () {
var that=this;
bridgeAPI.fhemSetFieldValue("WZ.harmony","activity", "Fernsehen")
.then(function(){
that.emit(':tell', utils.getRandomOKResponse());
});
},
Can someone try a similar Intent setup using any arbitrary Promise call?
I also had to propogate "this" to a variable so its available in the callback
var alexasdk = this;
request('http://www.google.com', function (error, response, body) {
console.log('error:', error); // Print the error if one occurred
console.log('statusCode:', response && response.statusCode);
alexasdk.emit(':tellWithCard'......
});
after the response from request lib, the this.emit is lost. Perhaps a more elegant way, do authors of AlexaSDK suggest anything?
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 still the "alexasdk" callback.
Hope this helps you @FerventGeek and @bjm88.
Complete fix for the original issue on top:
const Alexa = require('alexa-sdk');
const https = require('https');
const handlers = {
'GoGetRESTfoo': function () {
console.log("GoGetRESTfoo called");
promisedRESTrequest('Some foo bar url').then((response) => {
console.log("Success!", response); // Yea, REST all the things
this.emit(':tell', 'ok');
}, (error) => {
console.error("Failure!", response); // Boo, bad feels
this.emit(':tell', 'Hmm.. that didn\'t work. Check the CloudWatch Luke.');
});
},
// lots more handlers..
}
exports.handler = (event, context) => {
const alexa = Alexa.handler(event, context);
alexa.APP_ID = APP_ID;
alexa.registerHandlers(handlers);
alexa.execute();
};
this. <meta/>
Thx!
resolved by @jerolimov
closing
@jerolimov Have you made your example work correctly when the Promise is rejected?
I'm trying this now and I can fulfill the promise fine using arrow notation but if the promise is rejected I always get UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): TypeError: Cannot read property 'type' of null
This is the same issue @schaze mentioned earlier in this thread and I think not related to the binding of this.
Has anyone successfully rejected a promise in a skill handler?
To follow up on my earlier post, this was an issue in my code.
I had a catch handler something like this:
}).catch(() => {
this.emit(':tell', Messages.ERROR);
});
What was actually happening is that I had forgotten to export ERROR and this was getting passed as a null. This threw an error inside the SDK which the promise then caught as an unhandled rejection. I found this article on Promises and EventEmitters which explained it quite well https://qubyte.codes/blog/promises-and-nodejs-event-emitters-dont-mix
Once I fixed the underlying error the promise rejection started working fine. In case anyone else comes across this I found using setImmediate() was a nice way to get the actual error to be logged in cloudwatch instead of the unhandled rejection so adding this temporarily is quite useful to aid debugging. For example:
}).catch(() => {
setImmediate(() => {
this.emit(':tell', Messages.ERROR);
});
});
@jerolimov Thanks a lot.
Most helpful comment
The keyword
thisis bounded to the lastfunctionscope, 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 thethis-scope, so thatthisis still the "alexasdk" callback.Hope this helps you @FerventGeek and @bjm88.
Complete fix for the original issue on top: