I'm new to promises, async/await, and Alexa/lambda so bear with me.
My function is returning prior to the data being returned. I had a similar issue where I was getting an error, but have since edited my function quite a bit and therefore asking a new question. I'm now no longer getting an error, but instead my data is being returned first, then the promise is executing.
I've tried re-writing the promise/function after reading many, many SO, google, and amazon developer forums. Nothing seems to be working for me.
const IntentRequest = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest';
},
async handle(handlerInput) {
const { requestEnvelope, serviceClientFactory, responseBuilder } = handlerInput;
let responseData, promise;
checkAuthenticationStatus(handlerInput,function(json){
console.log('waited!')
if(json.error) {
return handlerInput.responseBuilder.speak(messages.NO_ACCESS).withSimpleCard('Unauthorized Request', messages.NO_ACCESS).getResponse();
} else if(json.noerror && json.noerror.okay == 'true'){
console.log('starting to get intent data')
const url = new URL(json.noerror.okay.path);
promise = new Promise((resolve, reject) => {
console.log('start promise')
return httpsGetIntent(handlerInput, url).then((resultData) => {
console.log(resultData)
responseData = resultData;
resolve(responseData)
console.log('inside promise, no error, prior to return data')
})
}).then((result) => { console.log('result', result)})
return handlerInput.responseBuilder.speak('Test').getResponse();
}
});
console.log('response data', responseData)
let result = await promise;
return result;
},
};
Out of my many console.logs() added for debugging, they print as follows:
- 'response data'
- 'waited!'
- 'starting to get intent data'
- 'start promise'
- resultData
- 'inside promise, no error, prior to return data'
While it's not fully fleshed out (I need to address the error handling portion) I wanted to share my solution here in case anyone stumbled upon this post and needed some help.
I moved the promise outside the checkAuthentication function and returned the data when it was processed. I then chained the promise with .then() and passed it the data returned, and prompted Alexa to speak.
const IntentRequest = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest';
},
async handle(handlerInput) {
const { requestEnvelope, serviceClientFactory, responseBuilder } = handlerInput;
let responseData, promise;
return new Promise((resolve, reject) => {
checkAuthenticationStatus(handlerInput, async function(json) {
if (json.error) {
return handlerInput.responseBuilder.speak(messages.NO_ACCESS).withSimpleCard('Unauthorized Request', messages.NO_ACCESS).getResponse();
} else if (json.noerror && json.noerror.okay == 'true') {
const url = new URL(json.noerror.okay.path);
let resultD = await httpsGetIntent(handlerInput, url, function(resultData) {
if (resultData) {
return resolve(resultData);
} else {
return resolve(handlerInput.responseBuilder.speak('False Test').getResponse());
}
})
}
})
}).then((data) => {
return handlerInput.responseBuilder.speak(data.response.outputSpeech.text).getResponse();
});
},
};
Almost_Ashleigh, based on your own answer, some ideas in which I have :
made the assumption that httpsGetIntent() returns a Promsie that delivers resultData.
promisified checkAuthenticationStatus() by writing the adaptor checkAuthenticationStatusAsync().
consolidated the .speak() commands into final .then() and .catch() clauses.
allowed specific errors to be tailored by decorating with a .title property, which is used in the final .catch() as a cardTitle. Any undecorated errors will default to 'Sorry' (or whatever you want).
// in a suitable scope ...
function checkAuthenticationStatusAsync(handlerInput) {
return new Promise((resolve, reject) {
checkAuthenticationStatus(handlerInput, (json) => {
if (json.error || !json.noerror || json.noerror.okay !== 'true') {
let err = new Error(messages.NO_ACCESS); // or maybe a separate error message per error case?
err.title = 'Unauthorized Request';
reject(err);
} else {
resolve(json);
}
});
});
}
// ... and ...
const IntentRequest = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest';
},
handle(handlerInput) {
return checkAuthenticationStatusAsync(handlerInput)
.then((json) => httpsGetIntent(handlerInput, new URL(json.noerror.okay.path)))
.then((resultData) => {
if (resultData) {
// success path ends here
return handlerInput.responseBuilder.speak(resultData.response.outputSpeech.text).getResponse();
} else {
throw new Error('No result data returned'); // throws to the .catch() below
}
})
.catch(err => {
// all errors end up here.
return handlerInput.responseBuilder.speak(err.message).withSimpleCard(err.title || 'Sorry', err.message).getResponse();
throw err; // to keep handle's caller informed
});
},
};
Note, this is a way, not necessarily the way, to write the code. Please feel free to raid for ideas.
Your best friend is async/await. Please use something like this or like this to access APIs.
Related
In short, I am making a Discord bot with discord.js and I am having trouble with asynchronous and synchronous functions.
In order to assign variables, the last part of the function loops through each variable and then converts it to its desired type, which can be seen here:
argsList.forEach((argument, index) => {
let finalArgument = argument
const type = args[index].type
if (type === UserArgument) {
new UserArgument(argument).result
.then(userObject => {
finalArgument = userObject
})
.catch(error => {
throw error
})
} else if (type === MemberArgument) {
new MemberArgument(argument, guild).result
.then(memberObject => {
finalArgument = memberObject
})
.catch(error => {
throw error
})
} else if (type === ChannelArgument) {
new ChannelArgument(argument, guild).result
.then(channelObject => {
finalArgument = channelObject
})
.catch(error => {
throw error
})
} else if (type === RoleArgument) {
new RoleArgument(argument, guild).result
.then(roleObject => {
finalArgument = roleObject
})
.catch(error => {
throw error
})
}
finalArgList.push(finalArgument)
})
return finalArgList
}
And here is an example of how the UserArgument class looks like (all other argument bases basically look the same)
class UserArgument {
constructor(user) {
this.result = new Promise((resolve, reject) => {
if (typeof(user) === "string") {
if (user.match(/^<#!([0-9]+)>$/)) {
user = user.match(/[0-9]+/)
}
if (isNumeric(user)) {
ArgumentBase.client.users.fetch(user)
.then(userObject => {
resolve(userObject)
return userObject
})
}
this.#getUserFromName(user)
.then(userObject => {
resolve(userObject)
return userObject
})
} else if (user instanceof DiscordJS.User) {
resolve(user)
return user
} else if (user instanceof DiscordJS.GuildMember || user instanceof DiscordJS.ThreadMember) {
resolve(user.user)
return user.user
}
let userObject = ArgumentBase.client.users.resolve(user)
if (userObject) {
resolve(userObject)
return userObject
}
reject(new UserNotFound(toString(user)))
})
}
async #getUserFromName(username) {
return new Promise((resolve, reject) => {
for (const user in ArgumentBase.client.users.cache) {
if (user.username === username) {
resolve(user)
return user
}
}
throw new UserNotFound(username)
})
}
}
The issue that I am coming across is that the code that handles each argument does not wait for the function to be finished and instead skips over it. This of course causes the command to be executed before the arguments are even processed. For my tests, I was testing throwing errors from the UserArgument class, and the error did get thrown, but only after the command had already executed because that is when it decided to finish.
My assumption is that since Promise is an asynchronous function, the code keeps running and does not wait for it. I tried turning the argument function to an async function and use await, but I kept getting the SyntaxError: await is only valid in async functions and the top level bodies of modules, even when the function is an async function (function declaration is static async getCommandArgs(invokedString, args, guild = undefined)). If someone could help me, that would be amazing. Thank you for your time and help.
My code is not running, can anybody help.
Unable to speak out the text, can i return handler input response. The test function is a http call which may take tieme.
function test(url, number)
{
return 5;
}
function speak(handlerInput) {
return handlerInput.responseBuilder
.getResponse();
}
const NumberFactIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& Alexa.getIntentName(handlerInput.requestEnvelope) === 'NumberFactIntent';
},
handle(handlerInput) {
const theNumber = handlerInput.requestEnvelope.request.intent.slots.number.value;
const repromptOutput = " Would you like another fact?";
const URL = "http://numbersapi.com";
test(URL, theNumber).then(function (data) {
console.log("data is " + data);
handlerInput.responseBuilder
.speak("Test Data")
.reprompt(repromptOutput)
return speak(handlerInput);
}).catch(function (data) {
console.log("error is " + data);
handlerInput.responseBuilder
.speak(`I wasn't able to find a fact for ${theNumber}` )
.reprompt(repromptOutput)
return speak(handlerInput);
});
}
};
First of all your test function doesn't return a promise. I don't know if this is intentional and you just cut api calling code to make it simpler, but it should return a promise if you want to use then on it.
If it returns a promise in your full example, then what are you missing is adding a return before test. Also you should return handlerInput from inside of your promise. Code should look like this (i'll remove some of the code, that is not relevant):
const NumberFactIntentHandler = {
canHandle(handlerInput) {},
handle(handlerInput) {
const repromptOutput = " Would you like another fact?";
const URL = "http://numbersapi.com";
return test(URL, theNumber).then(function (data) {
return handlerInput.responseBuilder
.speak("Test Data")
.reprompt(repromptOutput)
}).catch(function (data) {
return handlerInput.responseBuilder
.speak(`I wasn't able to find a fact for ${theNumber}` )
.reprompt(repromptOutput)
});
}
};
Now you might be wondering why do you need those return's. This is because JS functions implicitly returns undefined, so in this case you have to explicitly tell what should be returned from handle function. Same applies to inside of the promise.
This Code might Help You!!
//use request for http call
function fun(url) {
return new Promise((resolve, reject) => {
request.get(url,(err, res, body) => {
console.log("url-fetched");
return resolve(body);
});
});
}
const NumberFactIntentHandler = {
canHandle(handlerInput) {..
},
async handle(handlerInput) {
const theNumber =handlerInput.requestEnvelope.request.intent.slots.number.value;
const repromptOutput = " Would you like another fact?";
const URL = "http://numbersapi.com";
let data = await fun(url);
return handlerInput.responseBuilder
.speak(data)
.reprompt('is there any thing i can do for you?')
.withSimpleCard('Hello', speechText)
.getResponse();
};
I have two https requests in nodejs, the first to confirm a user is authorized to access the data, and the second is to return the data.
I have console logs throughout and I see the data is successfully being returned in the promise, but Alexa won't speak/show the card. There are no errors in cloudwatch.
I don't fully understand the promise syntax so I'm sure I'm missing something simple.
I've been changing up the syntax, trying async/await, and nothing seems to be working.
EDITED CODE - with some help, I was able to better lay out my code. I am now getting the error in cloud watch: ERROR: INVALID_RESPONSE, An exception occurred while dispatching the request to the skill.
Note: this error is occuring because right now I am FORCING an error, aka the top part (if redacted.errors).
messages.NO_ACCESS is currently set to ---- "Hmm, it seems like you don't have access to that data."
const IntentRequest = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest';
},
handle(handlerInput) {
const { requestEnvelope, serviceClientFactory, responseBuilder } = handlerInput;
let resp, resultData;
return new Promise((resolve, reject) => {
checkAuthenticationStatus(handlerInput, function(json){
if(REDACTED.errors) {
console.log('unauthed', REDACTED.error)
reject(handlerInput.responseBuilder.speak(messages.NO_ACCESS).withSimpleCard('Unauthorized Request', messages.NO_ACCESS).getResponse());
} else if(REDACTED.noerror && REDACTED.noerror.data == 'true'){
const url = new URL(REDACTED.url);
const resp = httpsGetIntent(handlerInput, url, (theResult) => {
resultData = theResult;
console.log('resolved with ---->', d)
return resolve(handlerInput.responseBuilder.speak("The result was" + d).withSimpleCard('Hello World', d).getResponse());
})
}
})
});
},
};
Here's the part of the code where the data IS (resultData and d are the same thing and both return data) returned but she doesn't speak/show card:
const IntentRequest = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest';
},
handle(handlerInput) {
const { requestEnvelope, serviceClientFactory, responseBuilder } = handlerInput;
let resp, resultData;
return new Promise((resolve, reject) => {
checkAuthenticationStatus(handlerInput, function(json){
if(REDACTED) {
reject(handlerInput.responseBuilder.speak(messages.NO_ACCESS).withSimpleCard('Unauthorized Request', messages.NO_ACCESS).getResponse())
} else if(REDACTED && REDACTED == 'true'){
const url = new URL(REDACTED);
resp = httpsGetIntent(handlerInput, url, (theResult) => {
resultData = theResult;
console.log(resultData)
}).then((d) => {
console.log(d)
resolve(handlerInput.responseBuilder.speak("The result was" + d.response.outputSpeech.text).withSimpleCard('Hello World', d.response.outputSpeech.text).getResponse());
}).catch((err) => { console.log(err)});
}
});
});
},
};
There are a few things you have to consider and are not clear in the question.
What is REDACTED here, where does that value come from ? you have not mentioned it anywhere.
Let's assume it's a global variable for the sake of convenience.
If REDACTED is false for any reason then your code is never gonna executed. Because the condition is never met. So that promise neither rejects nor resolves.
If am assuming you have mistakenly written REDACTED in the first if statement, it should be !REDACTED probably.
resp = httpsGetIntent(handlerInput, url, theResult => {
resultData = theResult;
console.log(resultData);
})
.then(d => {
console.log(d);
resolve(
handlerInput.responseBuilder
.speak("The result was" + d.response.outputSpeech.text)
.withSimpleCard("Hello World", d.response.outputSpeech.text)
.getResponse()
);
})
.catch(err => {
console.log(err);
});
Here the httpsGetIntent is also not correct, You are having a callback method which receives theResult and yet attaching a then method, which doesn't make any sense. Moreover resultData is not doing anything here.
Assuming that checkAuthenticationStatus and httpGetIntent both have a callback pattern, You could write something like this
const IntentRequest = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === "IntentRequest";
},
handle(handlerInput) {
const {
requestEnvelope,
serviceClientFactory,
responseBuilder
} = handlerInput;
let resp, resultData;
return new Promise((resolve, reject) => {
checkAuthenticationStatus(handlerInput, function (json) {
if (!REDACTED) {
reject(
handlerInput.responseBuilder
.speak(messages.NO_ACCESS)
.withSimpleCard("Unauthorized Request", messages.NO_ACCESS)
.getResponse()
);
} else if (REDACTED && REDACTED == "true") {
const url = new URL(REDACTED);
httpsGetIntent(handlerInput, url, theResult => {
resultData = theResult;
console.log(resultData);
return resolve(
handlerInput.responseBuilder
.speak("The result was" + d.response.outputSpeech.text)
.withSimpleCard("Hello World", d.response.outputSpeech.text)
.getResponse()
);
})
}
});
});
}
};
In arrow notation, .then((d) => resolve(x)) will return the resolve, because the call to resolve is on the same line, but if you .then(() => { // more than one line }) I think you just need to explicitly call return.
const IntentRequest = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest';
},
handle(handlerInput) {
const { requestEnvelope, serviceClientFactory, responseBuilder } = handlerInput;
// do you need resp here?
let resp, resultData;
return new Promise((resolve, reject) => {
checkAuthenticationStatus(handlerInput, function(json){
// does REDACTED know about json?
if(!REDACTED) {
reject(handlerInput.responseBuilder
.speak(messages.NO_ACCESS)
.withSimpleCard('Unauthorized Request', messages.NO_ACCESS)
.getResponse());
} else if(REDACTED && REDACTED == 'true') {
const url = new URL(REDACTED);
httpsGetIntent(handlerInput, url, (resultData) => {
console.log(resultData);
return resultData;
}).then((d) => {
resolve(handlerInput.responseBuilder
.speak("The result was" + d.response.outputSpeech.text)
.withSimpleCard("Hello World", d.response.outputSpeech.text)
.getResponse());
}).catch((err) => { console.log(err)});
}
});
});
}
},
To overcome callback hell in javascript, I'm trying to use async await from legacy code written in SQLServer procedure.
But I'm not sure my code might be write properly.
My first confusing point is when async function returns, should it return resolve() as boolean, or just return reject and handle with try-catch?
Here is my code snippets.
Please correct me to right direction.
apiRoutes.js
app.route('/api/dansok/cancelDansok')
.post(dansokCancelHandler.cancelDansok);
dansokCancelController.js
const sequelize = models.Sequelize;
const jwt = require('jsonwebtoken');
async function jwtAccessAuthCheck(accessToken) {
if (!accessToken) {
return Promise.reject('Empty access token');
}
jwt.verify(accessToken,"dipa",function(err){
if(err) {
return Promise.reject('TokenExpiredError.');
} else {
return Promise.resolve();
}
});
}
async function checkFeeHist(dansokSeqNo) {
let feeHist = await models.FeeHist.findOne({
where: { DansokSeqNo: dansokSeqNo}
});
return !!feeHist;
}
async function getNextDansokHistSerialNo(dansokSeqNo) {
....
}
async function getDansokFee(dansokSeqNo) {
....
}
async function doCancel(dansokSeqNo) {
try {
if (await !checkFeeHist(dansokSeqNo)) {
log.error("doCancel() invalid dansokSeqNo for cancel, ", dansokSeqNo);
return;
}
let nextDansokSerialNo = await getNextDansokHistSerialNo(dansokSeqNo);
await insertNewDansokHist(dansokSeqNo, nextDansokSerialNo);
await updateDansokHist(dansokSeqNo);
await updateVBankList(dansokSeqNo, danokFee.VBankSeqNo);
await getVBankList(dansokSeqNo);
} catch (e) {
log.error("doCancel() exception:", e);
}
}
exports.cancelDansok = function (req, res) {
res.setHeader("Content-Type", "application/json; charset=utf-8");
const dansokSeqNo = req.body.DANSOKSEQNO;
const discKindCode = req.body.HISTKIND;
const worker = req.body.PROCWORKER;
const workerIp = req.body.CREATEIP;
const accessToken = req.headers.accesstoken;
//check input parameter
if (!dansokSeqNo || !discKindCode || !worker || !workerIp) {
let e = {status:400, message:'params are empty.'};
return res.status(e.status).json(e);
}
try {
jwtAccessAuthCheck(accessToken)
.then(() => {
log.info("jwt success");
doCancel(dansokSeqNo).then(() => {
log.info("cancelDansok() finish");
res.status(200).json({ message: 'cancelDansok success.' });
});
});
} catch(e) {
return res.status(e.status).json(e);
}
};
You'll need to rewrite jwtAccessAuthCheck(accessToken) so that it keeps track of the outcome of its nested tasks. In the code you've written:
// Code that needs fixes!
async function jwtAccessAuthCheck(accessToken) {
// This part is fine. We are in the main async flow.
if (!accessToken) {
return Promise.reject('Empty access token');
}
// This needs to be rewritten, as the async function itself doesn't know anything about
// the outcome of `jwt.verify`...
jwt.verify(accessToken,"dipa",function(err){
if(err) {
// This is wrapped in a `function(err)` callback, so the return value is irrelevant
// to the async function itself
return Promise.reject('TokenExpiredError.');
} else {
// Same problem here.
return Promise.resolve();
}
});
// Since the main async scope didn't handle anything related to `jwt.verify`, the content
// below will print even before `jwt.verify()` completes! And the async call will be
// considered complete right away.
console.log('Completed before jwt.verify() outcome');
}
A better rewrite would be:
// Fixed code. The outcome of `jwt.verify` is explicitly delegated back to a new Promise's
// `resolve` and `reject` handlers, Promise which we await for.
async function jwtAccessAuthCheck(accessToken) {
await new Promise((resolve, reject) => {
if (!accessToken) {
reject('Empty access token');
return;
}
jwt.verify(accessToken,"dipa",function(err){
if(err) {
reject('TokenExpiredError.');
} else {
resolve();
}
});
});
// We won't consider this async call done until the Promise above completes.
console.log('Completed');
}
An alternate signature that would also work in this specific use case:
// Also works this way without the `async` type:
function jwtAccessAuthCheck(accessToken) {
return new Promise((resolve, reject) => {
...
});
}
Regarding your cancelDansok(req, res) middleware, since jwtAccessAuthCheck is guaranteed to return a Promise (you made it an async function), you'll also need to handle its returned Promise directly. No try / catch can handle the outcome of this asynchronous task.
exports.cancelDansok = function (req, res) {
...
jwtAccessAuthCheck(accessToken)
.then(() => {
log.info("jwt success");
return doCancel(dansokSeqNo);
})
.then(() => {
log.info("cancelDansok() finish");
res.status(200).json({ message: 'cancelDansok success.' });
})
.catch(e => {
res.status(e.status).json(e);
});
};
I strongly suggest reading a few Promise-related articles to get the hang of it. They're very handy and powerful, but also bring a little pain when mixed with other JS patterns (async callbacks, try / catch...).
https://www.promisejs.org/
Node.js util.promisify
Using promises with NodeJS, I load a model that can then be re-used by susequent calls to the NodeJS app. How can I prevent the same object/model being loaded twice from a database if a second request arrives while the first is still being loaded?
I set a "loading flag" to say that the object is being retrieved from the database and "loaded" when done. If there is a second request that attempts to load the same object, it needs to wait until the initial model is filled and then both can use the same object.
Sample Code (simplified, ES6, Node 0.10 [old for a reason]).
It's the TODO that needs solving.
App:
import ClickController from './controllers/ClickController'
import express from 'express'
const app = express()
app.get('/click/*', (req, res) => {
// Get the parameters here
let gameRef = "test";
ClickController.getGameModel(gameRef)
.then(() => {
console.log('Got game model')
return this.handleRequest()
}, (err) => {
throw err
})
}
Controller:
import gameModel from '../models/GameModel'
class ClickController {
constructor(config) {
// Stores the objects so they can be re-used multiple times.
this.loadedGames = {}
}
// getGameModel() as a promise; return model (or throw error if it doesn't exist)
getGameModel(gameRef) {
return new Promise((resolve, reject) => {
let oGame = false
if(typeof this.loadedGames[gameRef] === 'undefined') {
oGame = new gameModel()
this.loadedGames[gameRef] = oGame
} else {
oGame = this.loadedGames[gameRef]
}
oGame.load(gameRef)
.then(function() {
resolve()
}, (err) => {
reject(err)
})
})
}
}
Model / Object:
class GameModel {
constructor {
this.loading = false
this.loaded = false
}
load(gameRef) {
return new Promise((resolve, reject) => {
if (this.loading) {
// TODO: Need to wait until loaded, then resolve or reject
} else if (!this.loaded) {
this.loading = true
this.getActiveDBConnection()
.then(() => {
return this.loadGame(gameRef)
}, (err) => {
console.log(err)
reject(err)
})
.then(() => {
this.loading = false
this.loaded = true
resolve()
})
} else {
// Already loaded, we're fine
resolve()
}
})
}
// As this uses promises, another event could jump in and call "load" while this is working
loadGame(gameRef) {
return new Promise((resolve, reject) => {
let sql = `SELECT ... FROM games WHERE gameRef = ${mysql.escape(gameRef)}`
this.dbConnection.query(sql, (err, results) => {
if (err) {
reject('Error querying db for game by ref')
} else if (results.length > 0) {
// handle results
resolve()
} else {
reject('Game Not Found')
}
})
})
}
}
I don't follow exactly which part of you're code you are asking about, but the usual scheme for caching a value with a promise while a request is already "in-flight" works like this:
var cachePromise;
function loadStuff(...) {
if (cachePromise) {
return cachePromise;
} else {
// cache this promise so any other requests while this one is stil
// in flight will use the same promise
cachePromise = new Promise(function(resolve, reject) {
doSomeAsyncOperation(function(err, result) {
// clear cached promise so subsequent requests
// will do a new request, now that this one is done
cachePromise = null;
if (err) {
reject(err);
} else {
resolve(result);
}
});
});
return cachePromise;
}
}
// all these will use the same result that is in progress
loadStuff(...).then(function(result) {
// do something with result
});
loadStuff(...).then(function(result) {
// do something with result
});
loadStuff(...).then(function(result) {
// do something with result
});
This keeps a cached promise and, as long as request is "in-flight", the cachePromise value is in place and will be returned by subsequent requests.
As soon as the request actually finishes, the cachePromise will be cleared so that the next request that comes later will issue a new request.