Use Dynasty DynamoDB Query in Alexa Response - node.js

I am using Dynasty in my Nodejs Alexa Skill Service, run on AWS Lambda, to query DynamoDB. Due to the structure of the API, I am unable to use a query's result in my Alexa response. In the code below, the callback passed to 'then' is run after the handler returns, so 'name' is never assigned. How can I use information obtained in the query callback in my response?
const dynasty = require('dynasty')(credentials);
const myIntentHandler = {
canHandle(input) {
return input.requestEnvelope.request.intent.name === 'MyIntent';
},
handle(input) {
const userId = input.requestEnvelope.session.user.userId;
const users = dynasty.table('user');
var name;
users.find(userId).then(function(user) {
if(user) {
name = user.name;
} else {
...
}
});
return input.responseBuilder.speak('Hello ' + name).getResponse();
}
};

The Alexa SDK for NodeJS v2 supports promises in handlers.
So, from you handler you return a promise, chained off of the Dynasty query promise.
const dynasty = require('dynasty')(credentials);
const myIntentHandler = {
canHandle(input) {
return input.requestEnvelope.request.intent.name === 'MyIntent';
},
handle(input) {
const userId = input.requestEnvelope.session.user.userId;
const users = dynasty.table('user');
var name;
return new Promise((resolve, reject) => {
users.find(userId).then(function(user) {
if(user) {
name = user.name;
let response
= input.responseBuilder
.speak('Hello ' + name)
.getResponse();
resolve(response);
} else {
...
// handle this case
// and resolve(..) or reject(..)
}
});
});
}
};

Related

Callable cloud functions - handle error in android

im trying to delete an user from firestore and from auth.
I have this callable cloud function:
export const deleteUser = functions.https.onCall(async (data, context) => {
const userEmail = data.userEmail;
const collection = data.collection;
try {
deleteUserByEmail(userEmail, collection)
return "deleted!"
} catch (error) {
throw new functions.https.HttpsError('invalid-argument', 'there is no user with that email', error);
}
})
async function deleteUserByEmail(userEmail: string, collection: string) {
const auth = admin.auth();
const db = admin.firestore();
const { uid } = await auth.getUserByEmail(userEmail);
await db.collection(collection)
.doc(uid)
.delete();
await auth.deleteUser(uid);
return uid;
}
in android i have this:
fun deleteFromFirebase(){
val data = hashMapOf(
"userEmail" to user.email,
"collection" to "User"
)
functions // Optional region: .getInstance("europe-west1")
.getHttpsCallable("deleteUser")
.call(data)
.addOnCompleteListener() { task ->
if(!task.isSuccessful)
{
Log.d("User", "ERROR")
val e = task.exception
if (e != null) {
Log.d("Admin", e.message.toString())
}
}else{
Log.d("User", "Deleted")
//make something
}
}
}
If the user in auth and the document nin firestore exist, works great.
But i tryed to generate some error.
So I deleted the user from auth and ran the function. The Android log says D/User: User deleted
but in the console from google cloud:
Function execution took 1878 ms, finished with status code: 200
Exception from a finished function: Error: There is no user record corresponding to the provided identifier.
How can I handle the error and get correctly in android? Thanks!
The deleteUserByEmail function is async and returns a Promise. Your return statement runs before the promises is resolved. Try refactoring the code as shown below:
export const deleteUser = functions.https.onCall(async (data, context) => {
const userEmail = data.userEmail;
const collection = data.collection;
try {
// add await, continues after Promise is resolved
await deleteUserByEmail(userEmail, collection)
return "deleted!"
} catch (error) {
console.log(error) // <-- check for any errors
throw new functions.https.HttpsError('invalid-argument', 'there is no user with that email', error);
}
})
async function deleteUserByEmail(userEmail: string, collection: string) {
const auth = admin.auth();
const db = admin.firestore();
const { uid } = await auth.getUserByEmail(userEmail);
return await Promise.all([
db.collection(collection).doc(uid).delete(),
auth.deleteUser(uid)
])
}

Async function to scrape subreddits using Cheerio returns undefined

The script by itself works great (entering the url manually, writing a json file using the fs module, node script_name.js) but within a Express get request it returns undefined.
So I've built a simple frontend to let the user enter the subreddit name to be scraped.
And here's where the problem is:
Express controller
const run = require("../run");
requestPosts: async (req, res) => {
try {
const { subreddit } = req.body;
const response = await run(subreddit);
//console.log(response);
res.json(response);
} catch (error) {
console.error(error);
}
},
Cheerio functions
const axios = require("axios");
const { load } = require("cheerio");
let posts = [];
async function getImage(postLink) {
const { data } = await axios(postLink);
const $ = load(data);
return $("a.post-link").attr("href");
}
async function run(url) {
try {
console.log(url);
const { data } = await axios(url);
const $ = load(data);
$(".thing.linkflair.link").map(async (i, e) => {
const title = $(e)
.find(".entry.unvoted .top-matter .title .title")
.text();
const user = $(e)
.find(".entry.unvoted .top-matter .tagline .author")
.text();
const profileLink = `https://old.reddit.com/user/${user}`;
const postLink = `https://old.reddit.com/${$(e).find("a").attr("href")}`;
// const thumbail = $(e).find("a img").attr("src");
const image = await getImage(postLink);
posts.push({
id: i + 1,
title,
postLink,
image,
user: { user, profileLink },
});
});
const nextPage = $(".next-button a").attr("href");
if (nextPage) {
await run(nextPage);
} else {
return posts;
}
} catch (error) {
console.error(error);
}
}
module.exports = run;
I've tried working with Promise((resolve, reject) => {}).
I think it's returning undefined because maybe the code its not synchronized.
(idk if it makes sense, i've just started programming)
.map() is not promise-aware and does not wait for your promises to finish. So, $(".thing.linkflair.link").map() finishes long before any of the asynchronous functions inside its callback do. Thus you try to return posts BEFORE it has been populated.
Passing an async callback to .map() will return an array of promises. You can use Promise.all() on those promises to know when they are done and once you're doing that, you may as well just return each post object rather that using a higher level scoped/shared object, thus making the code more self contained.
I would suggest this code:
async function run(url) {
try {
console.log(url);
const { data } = await axios(url);
const $ = load(data);
const posts = await Promise.all($(".thing.linkflair.link").map(async (i, e) => {
const title = $(e)
.find(".entry.unvoted .top-matter .title .title")
.text();
const user = $(e)
.find(".entry.unvoted .top-matter .tagline .author")
.text();
const profileLink = `https://old.reddit.com/user/${user}`;
const postLink = `https://old.reddit.com/${$(e).find("a").attr("href")}`;
// const thumbail = $(e).find("a img").attr("src");
const image = await getImage(postLink);
// return a post object
return {
id: i + 1,
title,
postLink,
image,
user: { user, profileLink },
};
}));
const nextPage = $(".next-button a").attr("href");
if (nextPage) {
const newPosts = await run(nextPage);
// add these posts to the ones we already have
posts.push(...newPosts);
}
return posts;
} catch (error) {
console.error(error);
}
}

Async Firebase Cloud Function trigger - What to return on catch block?

I have written the trigger below and I'm not sure what I should return in case a catch block is called. I know that Firebase docs say that triggers should always return a Promise...
exports.sendPushNotificationForNewMessage = functions.firestore.document("messages/{messageId}").onCreate(async (snap, context) => {
const message = snap.data()
const chatRoomId = message.chatRoomId
const senderId = message.user.id
const senderUsername = message.user.username
try {
const chatRoom = await admin.firestore().collection("chatRooms").doc(chatRoomId).get()
const receiverId = chatRoom.data().userIds.find(userId => userId != senderId)
const receiver = await admin.firestore().collection("users").doc(receiverId).get()
const deviceToken = receiver.data().deviceToken
if (deviceToken) {
const payload = {
notification: {
title: "popster",
body: `New DM from ${senderUsername} 💬`,
badge: "1",
sound: "pop.m4a"
},
data: {
}
}
console.log(payload);
return admin.messaging().sendToDevice(deviceToken, payload)
} else {
return null
}
} catch (error) {
return null
}
})
The async function wraps your response in a Promise so here your return type is Promise<MessagingDevicesResponse | null> and that will terminate the Cloud Function.
I'm not sure what I should return in case a catch block is called.
Background functions do not return any value/error to client so you can just return null;.
Also checkout this Firecast for more information.

Node postgres query returning undefined instead of query result

I am creating a query to my postgres database. The function that makes the query looks like this:
const { pool } = require("./database");
async function getClasses(user) {
return pool.connect(async function (err, client, done) {
if (err) {
console.log(err);
} else {
const sqlText = `SELECT * FROM "public"."classes" WHERE "admins" = $1`;
const values = [user];
let listOfClasses = await client.query(sqlText, values);
done();
console.log(listOfClasses.rows);
return listOfClasses.rows;
}
});
}
module.exports = { getClasses };
The console.log(listOfClasses.rows) has the rows that I am looking for my function to return, but the actual returned value is undefined. I've been tinkering with the code for quite a while now and can't seem to figure it out. Any help here would be much appreciated.
You have to use the promise style api as the callback style call will not return anything (Your function is retuning client.connect(() => { ... }) which return undefined)
const getClasses = (...) => {
const client = await pool.connect()
const listOfClasses = await client.query(...);
return listOfClasses.rows;
}
should do

Return value after all the promises are resolved

I am working on a nodejs code that fetches data from a site, parses it, finds particular data and fetches something else for the data that was previously fetched. But the final return statement is returning without the value fetched from the second API call.
I tried to implement async await, but I am not sure where do I have to put them exactly.
const getMainData = async val => {
let result = [];
//get xml data from the API
const xmlData = await getSiteContent(`value`); //axios call
parseString(xmlData, (err, json) => { //convert xml to json
const { entry } = json.feed; // array of results.
result = entry.map(report => {
const secondInfo = getSomeMoreData(report.something); //axios call
const data = {
id: report.id,
date: report.date,
title: report.title
};
data.info = secondInfo;
return data;
});
});
return { result };
};
I was expecting the function to return the array result that has id, date, title and info. But I am getting info as null since it is going to another function that does one more API call.
Try wrapping parseString in a promise so you can await the result, then make the entry.map callback an async function so that you can use the await keyword to wait for the result of the axios fetch.
async function xml2json(xml) {
return new Promise((resolve, reject) => {
parseString(xml, function (err, json) {
if (err)
reject(err);
else
resolve(json);
});
});
}
const getMainData = async val => {
//get xml data from the API
const xmlData = await getSiteContent(`value`); //axios call
const json = await xml2json(xmlData);
const { entry } = json.feed; // array of results
const result = await Promise.all(
entry.map(async report => {
const secondInfo = await getSomeMoreData(report.something); // axios call
const data = {
id: report.id,
date: report.date,
title: report.title,
};
data.info = secondInfo;
return data;
})
)
return { result };
}
Let me know if that works. If not, I can try to help you out further.
The problem with your code is you have mixed promises concept(async/await is a syntactic sugar - so same thing) along with callback concept.
And the return statement is outside callback() of parseString() and the callback would be executed maybe after returning results only because parseString() is an asynchronous function.
So in the following solution I have wrapped parseString() in a promise so that it can be awaited.
const parseStringPromisified = async xmlData => {
return new Promise((resolve, reject) => {
parseString(xmlData, (err, json) => {
if (err) {
reject(err);
}
resolve(json);
});
});
};
const getMainData = async val => {
//get xml data from the API
const xmlData = await getSiteContent(`value`); //axios call
const json = await parseStringPromisified(xmlData);
const { entry } = json.feed; // array of results.
const result = entry.map(async report => {
const secondInfo = await getSomeMoreData(report.something); //axios call
return {
id: report.id,
date: report.date,
title: report.title,
info: secondInfo
};
});
return Promises.all(result);
};

Resources