Async Function in AWS Lambda Alexa Intent with Yummly API - javascript

I'm trying to retrieve data from the Yummly API through Amazon Alexa using ws-yummly in Node.js deployed on AWS Lambda. I'm fairly new to all aspects of this, but new to javascript in particular (Python is my 'native' language).
Here is what I have for my recommendation intent:
"RecommendationIntent": function () {
// delegate to Alexa to collect all the required slots
let filledSlots = delegateSlotCollection.call(this);
if (!filledSlots) {
return;
}
console.log("filled slots: " + JSON.stringify(filledSlots));
// at this point, we know that all required slots are filled.
let slotValues = getSlotValues(filledSlots);
console.log(JSON.stringify(slotValues));
const mainIngredientQuery = slotValues.mainIngredient.resolved;
async function main (queryWord) {
const resp = await Yummly.query(queryWord)
.maxTotalTimeInSeconds(1400)
.maxResults(20)
.minRating(3)
.get();
const names = resp.matches.map(recipe => recipe.recipeName);
const speechOutput = String(names[0]);
this.response.speak(speechOutput);
this.emit(":responseReady");
}
main(mainIngredientQuery).catch(error => console.error(error))
},
This is in the index.js file deployed on AWS lambda. I have isolated the problem to the async function. I have tested the function locally and it returns to console.log a list of recipe names. I want to have Alexa say these names. Or at least one name.
If I put the speechOutput assignment inside (as it is now), then I get an error that the 'Speechlet Response is set to null'.
If I tell it to 'return names' and set the external assignment to names or names[0] I get object promise or undefined (respectively).
Everything else in my program works fine and test these two bits apart they work, but putting them together doesn't work. I think that this is a syntax or placement error, but I don't understand the structure or formatting well enough yet (still learning) to understand what to try next.

How about using Promise.then like this:
async function main (queryWord) {
const resp = await Yummly.query(queryWord)
.maxTotalTimeInSeconds(1400)
.maxResults(20)
.minRating(3)
.get();
const names = resp.matches.map(recipe => recipe.recipeName);
return String(names[0]);
}
main(mainIngredientQuery)
.catch( error => console.error(error) )
.then( data => {
this.response.speak( data );
this.emit(":responseReady");
});

I'm updating this in case anyone else has the same problem.
If you notice, in my original code, I had an async function inside the intent. That didnt work because the intent itself was/is a function. By making the intent function an async function instead, I was able to solve the problem.
The following is working code for an async/await Alexa Intent.
The full index.js is available on my github if you want to take a look, but that will be a more advanced final version. The code below immediately follows up on the original question.
"RecommendationIntent": async function main () {
// delegate to Alexa to collect all the required slots
let filledSlots = delegateSlotCollection.call(this);
if (!filledSlots) {
return;
}
console.log("filled slots: " + JSON.stringify(filledSlots));
// at this point, we know that all required slots are filled.
let slotValues = getSlotValues(filledSlots);
console.log(JSON.stringify(slotValues));
const mainIngredientQuery = slotValues.mainIngredient.resolved;
const resp = await Yummly.query('chicken')
.maxTotalTimeInSeconds(1400)
.maxResults(20)
.minRating(3)
.get();
const names = resp.matches.map(recipe => recipe.recipeName);
console.log(names);
const speechOutput = names[0];
this.response.speak(speechOutput);
this.emit(":responseReady");
},

Related

Perform fetch request within a Firestore transaction: receiving "Cannot modify a WriteBatch that has been committed"

I'm trying to perform a fetch request within a transaction but when the code executes I receive the following error.
Error: Cannot modify a WriteBatch that has been committed.
The steps the function is performing are the following:
Compute document references (taken from an external source)
Query the documents available in Firestore
Verify if document exists
Fetch for further details (lazy loading mechanism)
Start populating first level collection
Start populating second level collection
Below the code I'm using.
await firestore.runTransaction(async (transaction) => {
// 1. Compute document references
const docRefs = computeDocRefs(colName, itemsDict);
// 2. Query the documents available in Firestore
const snapshots = await transaction.getAll(...docRefs);
snapshots.forEach(async (snapshot) => {
// 3. Verify if document exists
if (!snapshot.exists) {
console.log(snapshot.id + " does not exists");
const item = itemsDict[snapshot.id];
if (item) {
// 4. Fetch for further details
const response = await fetchData(item.detailUrl);
const detailItemsDict = prepareDetailPageData(response);
// 5. Start populating first level collection
transaction.set(snapshot.ref, {
index: item.index,
detailUrl: item.detailUrl,
title: item.title,
});
// 6. Start populating second level collection
const subColRef = colRef.doc(snapshot.id).collection(subColName);
detailItemsDict.detailItems.forEach((detailItem) => {
const subColDocRef = subColRef.doc();
transaction.set(subColDocRef, {
title: detailItem.title,
pdfUrl: detailItem.pdfUrl,
});
});
}
} else {
console.log(snapshot.id + " exists");
}
});
});
computeDocRefs is described below
function computeDocRefs(colName, itemsDict) {
const identifiers = Object.keys(itemsDict);
const docRefs = identifiers.map((identifier) => {
const docId = `${colName}/${identifier}`
return firestore.doc(docId);
});
return docRefs;
}
while fetchData uses axios under the hood
async function fetchData(url) {
const response = await axios(url);
if (response.status !== 200) {
throw new Error('Fetched data failed!');
}
return response;
}
prepareMainPageData and prepareDetailPageData are functions that prepare the data normalizing them.
If I comment the await fetchData(item.detailUrl), the first level collection with all the documents associated to it are stored correctly.
On the contrary with await fetchData(item.detailUrl) the errors happens below the following comment: // 5. Start populating first level collection.
The order of the operation are important since I do now want to make the second call if not necessary.
Are you able to guide me towards the correct solution?
The problem is due to the fact that forEach and async/await do not work well together. For example: Using async/await with a forEach loop.
Now I've completely changed the approach I'm following and now it works smoothly.
The code now is like the following:
// Read transaction to retrieve the items that are not yet available in Firestore
const itemsToFetch = await readItemsToFetch(itemsDict, colName);
// Merge the items previously retrieved to grab additional details through fetch network calls
const fetchedItems = await aggregateItemsToFetch(itemsToFetch);
// Write transaction (Batched Write) to save items into Firestore
const result = await writeFetchedItems(fetchedItems, colName, subColName);
A big thanks goes to Doug Stevenson and Renaud Tarnec.

Firebase cloud functions errors

Greetings of the day to everyone,
So I'm having a really hard time with Firebase cause there's just so many versions and things going on. Its extremely complicated. I wanted to achieve some functionality which is not available through the client modular web 9 version.
So I have been trying to use the Cloud functions to just get the list of all the collections in a document.
My cloud function looks like this so far -
const functions = require("firebase-functions");
const admin = require('firebase-admin');
const { object } = require("firebase-functions/v1/storage");
admin.initializeApp();
const db = admin.firestore();
exports.addMessages = functions.https.onCall(async (data, context) => {
// Grab the text parameter.
const original = data.text;
var test;
const writeResult = admin.firestore().collection('users').doc(original)
const collections = await writeResult.listCollections();
collections.forEach(collection => {
console.log('Found subcollection with id:', collection.id);
test = collection.id;
});
// Send back a message that we've successfully written the message
return { id: test }
});
I call this function on my front end simply like this --
const functions = getFunctions();
const addMessage = httpsCallable(functions, 'addMessages');
const Cloud = () => {
addMessage({ docPath: `users/${userProfile.uid}` })
.then(function (result) {
var collections = result.data.collections;
console.log(collections);
})
.catch(function (error) {
// Getting the Error details.
var code = error.code;
var message = error.message;
var details = error.details;
// ...
});
}
However, I get a 500 ERROR. I have checked the payload and everything, the data is being passed and the function is being called but there is some other kind of bug that I seem to be missing.
If anyone has any ideas please do let me know.
First off, you're calling addMessage with an Object parameter and then trying to access it's payload by the text field of data, which is undefined. You could either pass users/${userProfile.uid} as a string parameter or assign data.docPath to original.
Also, test will only contain the last collection id, since it's being overwritten forEach of the collections. So, may I suggest you make test a string array and push() each collection id to it?
Finally, on the callback of addMessage, there is no data field to access in result. In the case you decide to use an array, result will simply be the array you returned from the cloud function.
P.S. I'm not sure I see a reason why this wouldn't work with the V9 modular SDK.

Returning the value of an async axios API call to a variable [duplicate]

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 1 year ago.
I do apologise in advance for what seems like a stupid question that has been answered so many times before, however I just can't get my head around exactly how to accomplish what I'm trying to do and I've been going round and around in circles for hours now re-writing the same bit of code over and over again.
I am very much a beginner at any kind of programming (bar some SQL wrangling and a bit of dodgy VBA stuff) and find it difficult to learn without a specific problem I want to solve. I have done some basic javascript course stuff so I know the basic syntax.
What I am trying to do is make a GET request using axios to get some data from an api and use the data it responds with to set the value of a variable that I'm then using to display on the screen.
Essentially what I want to do is this (within a nodejs .js script):
Get data from a GET api call
Get a value from that data to make a second GET API call
Use the data from that second call to place into variables that I can use within a function
At the moment I'm stuck at the first hurdle, this is what I have so far (that works):
const queryData = '123456';
const getLink = async () => {
try {
const resp = await axios.get(`https://www.apisite.com/${queryData}`);
return resp.data;
} catch (err) {
console.error(err);
}
};
getLink().then(console.log);
And this does actually do something, the console is populated with the object returned by the API
Where I am stumped is how to get that data into my variable to use on screen.
Essentially what I want to do here is
let myObject = getLink();
From there I would then want to use the rest of the object so I could have:
let title = myObject.title;
let desc = myObject.description;
let user = myObject.user;
etc.
And this is where I am stuck because I can't seem to find a way to "get the data out" so to speak. I've tried suggestions about adding a new async function, about wrapping it as an IIFE and I just can't figure out what the answer is.
Any help at all (using as basic a concept as possible) would be much appreciated here.
#Bergi - the function I'm trying to call this is here. However I haven't actually been using this function to test, I've just been doing it from an empty project to just see if I can get the basics to work so I don't have to worry about other things obfuscating the issue. I'm trying to test it outside of any function at the moment just trying to see exactly how to get the data out to implement it. The below is the fabricated version of what I would be trying if I could get this to work but it is likely it is full of errors because I haven't actually tried to run this, I made it purely to show what I'm trying to do:
testing = function(tokens, idx, options, env, self) {
const token = tokens[idx];
if (token.info !== 'isbn') return defaultRender(tokens, idx, options, env, self); //checks for 'isbn' code fence token
const elementId = 'isbn_target_' + Math.random() + '_' + Date.now();
const element = document.createElement('div');
let html = '';
try {
element.setAttribute('id', elementId);
element.style.display = 'none';
document.body.appendChild(element);
//let queryData = token.content.trim();
const queryData = '123456';
const getLink = async () => {
try {
const resp = await axios.get(`https://www.apisite.com/${queryData}`);
return resp.data;
} catch (err) {
console.error(err);
}
};
let queryResponse = getLink();
let title = queryResponse.title;
html = '<div style="background-color: white;">' + title + '</div>';
} catch (error) {
console.error(error);
return '<div style="border: 1px solid red; padding: 10px;">Could not render preview: ' + htmlentities(error.message) + '</div>';
} finally {
document.body.removeChild(element);
}
return html;
};
Because getLink() is async, it returns a Promise.
In Node v14.18.0 and later, you can simply await it:
let myObject = await getLink();
You can wrap your code in an anonymous async function if you're running a prior version of Node:
(async () => {
let myObject = await getLink();
let title = myObject.title;
let desc = myObject.description;
let user = myObject.user;
})();
While outside the scope of the question as you've asked it here, you might also consider decreasing the verbosity of your code by using Destructuring assignment to assign your values:
let myObject = await getLink();
let {title, 'description': desc, user} = myObject;

Variable is used before its assignment typescript

I have a set of async tasks which is independent of each other. I have two questions, when considering the below code.
const parallelTasks: any = []
let email:string
//parallelTasks.push(otherPromises)
const PromiseTask1 = new Promise(async(resolve,reject)=>{
//some code
email = await GetEmail(connection)
resolve('')
})
parallelTasks.push(PromiseTask1)
// Scenario 1
await Promise.all(parallelTasks)
console.log(email) //this is giving me lint error "variable email is used before assignment"
// Scenario 2
Promise.all(parallelTasks).then(()=>{
console.log(email) //this is working fine
})
if i declare just like below, its not showing me any lint error. my questions are
let email: any
Is any includes Promise type as well?
Just consider am using scenario 1, Is await Promise.all() blocks the execution of all its below code, if yes, then I should not be getting the lint error when i used email: string. if no then, I Shouldnt be getting the value of email in console right. but surprisingly I got the resolved value. what am missing here?
Re #1
Yes, any includes any type, including Promise.
Re #2
I think you mean you're getting:
Variable 'email' is used before being assigned.(2454)
from the line using email in code similar to this:
(async () => {
const parallelTasks: any = []
let email:string
//parallelTasks.push(otherPromises)
const PromiseTask1 = new Promise(async(resolve,reject)=>{
//some code
email = await GetEmail(/*connection*/)
resolve('')
})
parallelTasks.push(PromiseTask1)
// Scenario 1
await Promise.all(parallelTasks)
console.log(email) //this is giving me error
})();
async function GetEmail() {
return "foo";
}
Playground link
If so, it's just that you've hit a limit on TypeScript's ability to determine that email has been assigned to. You're right, it has been assigned to, but it's done too indirectly for TypeScript to see it.
There are at least two ways to deal with that.
The first way
You could use the result of Promise.all, by removing your extra unnecessary new Promise call and using GetEmail directly:
(async () => {
const parallelTasks: any = [];
parallelTasks.push(GetEmail(/*connection*/))
const [email] = await Promise.all(parallelTasks);
console.log(email); // No error now
})();
async function GetEmail() {
return "foo";
}
Playground link
You'd adjust the destructuring to allow for the results of the other tasks you're putting into parallelTasks. For instance:
parallelTasks.push(task1());
parallelTasks.push(task2());
parallelTasks.push(GetEmail(connection));
parallelTasks.push(task4());
then it might be:
const [result1, result2, email, result4] = await Promise.all(paralleltasks);
or if you didn't need the others, just put GetEmail(connection) in at the front and use the original destructuring above (const [email] = ...).
The second way
You can tell TypeScript that you know email will be definitely assigned before you use it, using the definitely-assigned assertion (!):
let email!:string;
// ^

async function has old state React-Redux

I am working on a React project where user can upload and remove photos in projects. After Uploading new image it should be visible to user only if corresponding projectis selected. The solution is fairly simple to check
if
(selectedProject.projectID=== imageMetadata.projectID)
where
selectedProject.projectID: Id of currently selected project( Comming from
Redux Store )
imageMetadata.projectID: Id of project to which uploaded image belongs.
All of this is being done inside an async function and the problem we are facing is that even after selectedAlbum.albumID is changed everywhere else, this function still has its old value. Is it because the function is async?
This is my code:
let projectId = selectedProject.projectID;
const responses = await Promise.all(
filesToUpload.map( async (file) => {
let res = await imageStoreApiHandler.uploadPhoto(file, projectId);
notifyUploadProgress(count++, total, notifcationKey);
if (res.status === 200) {
let imageMetadata: any = res.data[0];
if (selectedProject.projectID === imageMetadata.projectID) {
addImage(imageMetadata);
}
}
return res;
}));
It's probably a closure problem where the uploadhandler keeps caching the first function.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures
Maybe write a function which will return a promise all array based on the parameters current project id.

Categories

Resources