Generator function with yields - javascript

I'm trying to do a generator function with yield inside a Redux saga.
I'm trying to get a value from my endpoint ind function g1 by calling function g2. Don't know id this is the right way to do it. I only want to get data from my endpoint if isTranslatable is true.
But fore some reason I get a response undefined. What am I missing. And yes I'm new at this.
service file
export const fetchEnglishFilterValue = async (
countrySelectorCountry: string,
selectedFilter: string,
selectedFilterValue: string,
): Promise<Response> => {
const url = `${DONORS_SERVICE_URL}/api/donors/translate/${countrySelectorCountry}/${selectedFilter}/${selectedFilterValue}`;
return get(url);
};
saga file
export function* g1() {
const englishFilterValue = g2(selectedFilter.key, selectedFilter.value);
console.log(isTranslatable ? englishFilterValue : "normal value");
console.log(englishFilterValue.next().value);
}
export function* g2(filterKey: string, filterValue: string) {
try {
const response: Response = yield call(fetchEnglishFilterValue, appSettings.language, filterKey, filterValue);
console.log("response: ", response);
const data: string = yield call([response, response.json]);
console.log("data: ", data);
return data;
} catch {
error(`Getting english filter value for datalayer. countrySelectorCountry: ${appSettings.language}, selectedFilter: ${filterKey}, selectedFilterValue: ${filterValue}`);
return undefined;
}
}

Related

How implement types properly?

I'm new to Typescript and have been doing a refactor a colleague code, I'm currently doing a typecheck and removing all any types. The goal is to make an MSGraph API call and return the a JSON file that translated into BirthdayPerson with a name, birthday date and a ID
I've been trying to a assign a type instead of any in the following code, but whether I assign number, string or any other type a different error will show up.
Perhaps I'm not tackling the solution correctly:
graph.ts
* #param accessToken
* #param endpoint url to call from MS Graph
*/
async function callMsGraph(accessToken: string, endpoint: string) {
const headers = new Headers();
const bearer = `Bearer ${accessToken}`;
headers.append('Authorization', bearer);
const options = {
method: 'GET',
headers,
};
try {
return fetch(endpoint, options);
} catch (error) {
console.error(error);
throw error;
}
}
export const callMsGraphWithoutPagination = async (
accessToken: string,
url: string,
dataToReturn: any[] = []
): Promise<any[]> => {
try {
const data = await callMsGraph(accessToken, url);
const dataJson = await data.json();
const newData = dataToReturn.concat(dataJson.value);
if (dataJson['#odata.nextLink']) {
const NEXT_URL = dataJson['#odata.nextLink'].split('/v1.0')[1];
return await callMsGraphWithoutPagination(
accessToken,
process.env.REACT_APP_GRAPH_URL + NEXT_URL,
newData
);
}
return dataToReturn.concat(dataJson.value);
} catch (error) {
/* eslint-disable no-console */
console.error(error);
/* eslint-enable no-console */
throw error;
}
};
export default callMsGraph;
useUsers.tsx
export const useUsers = () => {
const token = useToken();
const [users, setUsers] = React.useState<BirthdayPerson[]>([]);
React.useEffect(() => {
if (token) {
callMsGraphWithoutPagination(token, graphConfig.usersEndpoint).then(async (data: any) => {
const processedData: any[] = await Promise.all(
data.map(async (element: any) => {
const user = await callMsGraph(token, graphConfig.userBirthdayEndpoint(element.id));
const userJson = await user.json();
const image = await callMsGraph(token, graphConfig.userPhotoEndpoint(element.id));
const blob = await image.blob();
const returnElement: BirthdayPerson = {
displayName: element.displayName,
birthday: userJson.value,
id: element.id,
};
if (blob !== null) {
window.URL = window.URL || window.webkitURL;
returnElement.picture = window.URL.createObjectURL(blob);
}
return returnElement;
})
);
setUsers([].concat(...processedData));
});
}
}, [token]);
return users;
};
helpers.ts
interface IUpdateData {
slideCount: number;
}
const sortAndFilterBirthdays = (people: BirthdayPerson[], daysToGet: number) =>
people
.sort((firstEl, secondEl) => sortDate(firstEl.birthday, secondEl.birthday))
.filter(({ birthday }) => filterByAmountOfDays({ date: birthday, daysAfter: daysToGet }));
const getBirthdays: any = (people: BirthdayPerson[], daysToGet: number) => {
const validBirthdays = people.filter((element: any) => {
const year = moment(element.birthday).year();
return year !== 0;
});
const result = sortAndFilterBirthdays(validBirthdays, daysToGet);
// if it's okay
if (result.length > 1 && daysToGet <= 30) {
return result;
}
// if not okay, filters by future dates, concats with 'next year' dates, returns 2 dates
const fallbackResult = validBirthdays
.sort((firstEl, secondEl) => sortDate(firstEl.birthday, secondEl.birthday))
.filter((person: BirthdayPerson) => {
const currentYear = moment().year();
const date = moment(person.birthday, DATE_FORMAT).set('years', currentYear);
return moment().diff(date, 'days') <= 0;
});
return fallbackResult.concat(validBirthdays).splice(0, 2);
};
Any help or indication would be great!
From all the changes I've done another object will complain that Type 'x' is not assignable to type 'string'
Firstly you need to somehow type responses from API, because right now, as you have seen, call to .json() on Response object returns unknown, which make sense because no one knows what response the server returns. You may know what response it is expected to return, but not what it actually does.
Ideally therefore you need a parser that will verify that the response has correct structure and throws an error otherwise. There are libraries such as superstruct, yup, joi and others which you can use for this. Of course this is a lot of work and will need refactoring. Or if you don't care enough you can just cast the response object to appropriate type, but then if server returns something unexpected and the application cannot handle it, it's your fault.
Example with response parsing using superstruct
import {string, number, create, Infer} from 'superstruct'
// I assume `BirthdayUser` is just a string, but you can type more complex objects as well
const BirthdayUser = string()
// This is so that you don't have to list fields twice: once
// for the parser and once for typescript
type BirthdayUser = Infer<typeof BirthdayUser>
// Then use the parser
const response = await callMsGraph(acessToken, url)
const userJson = await response.json()
// user variable has inferred appropriate type, and if the response doesn't
// comply with the definition of `BirthdayUser` an error will be thrown
// Also I assume MS graph doesn't just return plain value but wraps it in an object with `value` field, so writing it here
const user = create(userJson, object({ value: BirthdayUser }))
Example of "I don't care enough" solution
type BirthdayUser = string
const response = await callMsGraph(accessToken, url)
// Same thing with wrapping into object with `value` field
const userJson = (await response.json()) as {value: BirthdayUser}
This is a bit awkward, because API call returns Response object and not the actual data. It might be easier to work with if you move parsing and casting logic inside of callMsGraph. It's not obligatory of course, but I still provide an example because after that you need to type callMsGraphWithoutPagination and it will use the same idea
import {object, create, Struct} from 'superstruct'
async function callMsGraphParsed<T>(
accessToken: string,
url: string,
// Since we need information about object structure at runtime, just making function
// generic is not enough, you need to pass the parser structure as an argument
struct: Struct<T>
) {
// ...
const response = await fetch(...)
const json = await response.json()
// Same, verifies that response complies to provided structure, if it
// does returns type object (of type `T`), otherwise throws an error
return create(json, object({ value: struct }))
}
async function callMsGraphLazy<T>(accessToken: string, url: string) {
// ...
const response = await fetch(...)
const json = await response.json()
return json as {value: T}
}
However I only call .json() here, if you want to use this solution, you will then need either a different function or another argument if you also want it to call .blob() for some API calls.
Now you type callMsGraphWithoutPagination using in the same way:
export const callMsGraphWithoutPaginationParsed = async <T>(
accessToken: string,
url: string,
dataToReturn: T[] = [],
struct: Struct<T>,
): Promise<T[]> => {
// If you rewrote `callMsGraph` to use parsing
const dataJson = await callMsGraph(accessToken, url, struct);
const newData = dataToReturn.concat(dataJson.value);
// ...
}
export const callMsGraphWithoutPaginationLazy= async <T>(
accessToken: string,
url: string,
dataToReturn: T[] = [],
): Promise<T[]> => {
// If you left `callMsGraph` as is
const data = await callMsGraph(accessToken, url);
const dataJson = (await data.json()) as {value: T}
const newData = dataToReturn.concat(dataJson.value);
// ...
}
Then use it
// Not sure if you are requesting `BirthdayUser` array here or some other entity, so change it to whatever you expect to receive
callMsGraphWithoutPagination<BirthdayUser>(token, graphConfig.usersEndpoint).then(async (data) => {
// "data" is inferred to have type BirthdayUser[]
data.map(element => {
// "element" is inferred to have type BirthdayUser
})
})
Also everywhere I wrote "I assume" and "Not sure" is missing info that you should probably have provided in the question. It didn't turn out to be relevant for me, but it could have. Good luck!

Error: Error serializing `.data[4].description` returned from `getStaticProps` in "/" [duplicate]

I'm working with Next.js, I tried accessing data but got this error:
Error: Error serializing `.profileData` returned from `getStaticProps` in "/profile/[slug]".
Reason: `undefined` cannot be serialized as JSON. Please use `null` or omit this value.
My code:
import { getAllBusinessProfiles } from '../../lib/api';
const Profile = ({ allProfiles: { edges } }) => {
return (
<>
<Head>
<title>Profile</title>
</Head>
<Hero />
<section>
{edges.map(({ node }) => (
<div key={node.id}>
<Link href={`/profile/${node.slug}`}>
<a> {node.businessInfo.name} </a>
</Link>
</div>
))}
</section>
</>
);
}
export default Profile;
export async function getStaticProps() {
const allProfiles = await getAllBusinessProfiles();
return {
props: {
allProfiles
}
};
}
getAllBusinessProfiles from api.js:
const API_URL = process.env.WP_API_URL;
async function fetchAPI(query, { variables } = {}) {
const headers = { 'Content-Type': 'application/json' };
const res = await fetch(API_URL, {
method: 'POST',
headers,
body: JSON.stringify({ query, variables })
});
const json = await res.json();
if (json.errors) {
console.log(json.errors);
console.log('error details', query, variables);
throw new Error('Failed to fetch API');
}
return json.data;
}
export async function getAllBusinessProfiles() {
const data = await fetchAPI(
`
query AllProfiles {
businessProfiles(where: {orderby: {field: DATE, order: ASC}}) {
edges {
node {
date
title
slug
link
uri
businessInfo {
name
title
company
image {
mediaItemUrl
altText
}
highlight
phone
city
country
facebook
instagram
email
website
profiles {
profile
profileInfo
}
extendedProfile {
title
info
}
}
}
}
}
}
`
);
return data?.businessProfiles;
};
What could be the error here? I used the getStaticProps method on Next.js but got the error above instead. Please, check. Thanks.
The error:
Server Error
Error: Error serializing .profileData returned from getStaticProps in "/profile/[slug]".
Reason: undefined cannot be serialized as JSON. Please use null or omit this value.
I don't know what could cause this though.
Add JSON.stringify when calling an asynchronous function that returns an object.
Try modifying your getStaticProps function like this.
export async function getStaticProps() {
const profiles = await getAllBusinessProfiles();
const allProfiles = JSON.stringify(profiles)
return {
props: {
allProfiles
}
};
}
The JSON.stringify() method converts a JavaScript object or value to a JSON string, optionally replacing values if a replacer function is specified or optionally including only the specified properties if a replacer array is specified.
Source: MDN
I had this issue using Mongoose and Next.js.
To solve it: I switched from convert require to import then wrapped my result in JSON.parse(JSON.stringify(result));.
Good: import mongoose from 'mongoose';
Bad: const mongoose = require('mongoose');
I had the same serialization error when accessing a Vercel system environment variable in getStaticProps.
Using JSON.stringify did not do the trick, but String() worked. My code:
export async function getStaticProps() {
const deploymentURL = String(process.env.NEXT_PUBLIC_VERCEL_URL);
return {
props: {
deploymentURL,
},
};
}
Thanks to this GitHub issue for the inspiration
I had the same issue when I was working with redux with next js and the reason was one of the fields in the default state I set it to undefined. Instead I used null:
const INITIAL_STATE = {
products: [],
loading: false,
error: undefined,
cart: [],
};
error:undefined was causing the error. Because "undefined" cannot be serialized:
export async function getStaticProps() {
const allProfiles = await getAllBusinessProfiles();
return {
props: {
allProfiles
}
};
}
you are returning "allProfiles" which is the result of async getAllBusinessProfiles() which is either returning undefined, error or one of the fields of the returned object is undefined. "error" object is not serializable in javascript
Instead of using undefined, you have to use null as the value for your variables.
Note that the error shows you exactly which variable is using undefined as its value. Just modify its value to be null.
The value 'undefined' denotes that a variable has been declared, but hasn't been assigned any value. So, the value of the variable is 'undefined'. On the other hand, 'null' refers to a non-existent object, which basically means 'empty' or 'nothing'.
Source: [1]
I was having the same issue while trying to find a match in the array of data using the id. The issue I had was the items in the array had ids which were numbers while the value I was getting from params was a string. So all i did was convert the number id to a string to match the comparison.
export async function getStaticProps({ params }) {
const coffeeStore = coffeeStoreData.find(
(store) => store.id.toString() === params.slug[0]
);
return {
props: {
coffeeStore,
},
};
}
install a package called babel-plugin-superjson-next and superjson and added a .babelrc file with these contents:
{
"presets": ["next/babel"],
"plugins": ["superjson-next"]
}
see this topic : https://github.com/vercel/next.js/discussions/11498.
I had a similar problem too where I was fetching data through apollo directly inside of getStaticProps. All I had to do to fix the error was add the spread syntax to the return.
return {
props: {
data: { ...data }
}
}
return { props: { allProfiles: allProfiles || null } }
In getStaticProps() function, after fetching your data it will be in json format initially, but you should change it as follow:
const res = await fetch(`${APP_URL}/api/projects`);
const data = JSON.parse(res);
now it will work.
When you call api you should use try catch. It will resolve error.
Example:
import axios from "axios";
export const getStaticProps = async () => {
try {
const response = await axios.get("http:...");
const data = response.data;
return {
props: {
posts: data
}
}
} catch (error) {
console.log(error);
}
}
Hope help for you !
put res from API in Curly Brackets
const { res } = await axios.post("http://localhost:3000/api", {data})
return { props: { res } }
try this, it worked for me:
export async function getStaticProps() {
const APP_URL = process.env.PUBLIC_NEXT_WEB_APP_URL;
const res = await fetch(`${APP_URL}/api/projects`);
const projects = await res.json();
return {
props: {
projects: projects?.data,
},
};
}

Next.js Error serializing `.res` returned from `getServerSideProps`

I am getting the error below, when I use getServerSideProps function to retrieve data from Binance API.
import binance from "../config/binance-config";
export async function getServerSideProps() {
const res = await binance.balance((error, balances) => {
console.info("BTC balance: ", balances.BTC.available);
});
return {
props: {
res,
},
};
}
import Binance from "node-binance-api"
const binance = new Binance().options({
APIKEY: 'xxx',
APISECRET: 'xxx'
});
export default binance;
Error output:
Error: Error serializing `.res` returned from `getServerSideProps` in "/dashboard".
Reason: `undefined` cannot be serialized as JSON. Please use `null` or omit this value.
I'm not sure how to resolve this error. I would just like to be able to mine (and display) the response by sending it as props in another component.
Thank you!
Here is how I solved it in NextJs
// Get Data from Database
export async function getServerSideProps(ctx) {
const { params } = ctx;
const { slug } = params;
await dbConnect.connect();
const member = await Member.findOne({ slug }).lean();
await dbConnect.disconnect();
return {
props: {
member: JSON.parse(JSON.stringify(member)), // <== here is a solution
},
};
}
Convert your data into json format when you are fetching it through an Api,
export async function getServerSideProps(context) {
const res = await fetch(`https://.../data`)
const data = await res.json()
if (!data) {
return {
redirect: {
destination: '/',
permanent: false,
},
}
}`enter code here`
return {
props: {}, // will be passed to the page component as props
}
}
You can read more detail on this link, https://nextjs.org/docs/basic-features/data-fetching#getserversideprops-server-side-rendering
put res from API in Curly Brackets
const { res } = await binance.balance((error, balances) => {
console.info("BTC balance: ", balances.BTC.available);
});
return {
props: {
res,
},
};
This is actually a simple error. The props that are being returned from getServerSideProps must be wrapped in curly brackets as shown below:
return {props: {res}}
This will clear the serialization error provided no nulls are being returned in response

How to compose/pipe functions when dealing with objects?

Say I have a Request object:
{
user: { /* user data: username, email, etc. */ }
post: { /* post data: content, date, etc. */ }
}
Example of a Request object:
{
user: {
id: '123'
username: 'kibe'
email: 'blabla#gmail.com'
}
post: {
content: 'my new post!'
date: '20/02/2004'
}
}
Now, I have two functions: validateUser and validatePost. Both of them return a Maybe monad, because they might fail.
How can I then do something like this?
function savePost (request) {
return request
|> validateUser // if validateUser returns either Success(user) or Failure(error), how would I pass down the post?
|> validatePost
|> savePostToDb
|> ok
}
Should I create a function validateRequest which composes validateUser and validatePost? But then, how would I only give the post object to savePostToDb? What if savePostToDb also requires the user ID?
function savePost (request) {
return request
|> validateRequest // returns an Either monad
|> savePostToDb // only requires user ID and the post, how would I pass these down?
|> ok
}
Hopefully these questions make sense. I am new to FP and although I am understanding its paradigms, I am failing to design a simple program
Thanks!
prop
We write a simple prop function which safely looks up properties on your object. If they are present, we get a Just-wrapped value, otherwise we get Nothing -
const fromNullable = x =>
x == null
? Nothing
: Just(x)
const prop = k => t =>
fromNullable(t[k])
props
You have nested properties though, so let's make it so we can dig arbitrarily deep into your object using any sequence of props -
const props = (k, ...ks) => t =>
ks.length
? prop(k)(t).bind(props(...ks))
: prop(k)(t)
validate
Now we can write a simple validate coroutine -
function* validate (t)
{ const id = yield props("user", "id")(t)
const username = yield props("user", "username")(t)
const email = yield props("user", "email")(t)
const post = yield props("post", "content")(t)
const date = yield props("post", "date")(t)
return Just({ id, username, email, post, date }) // <- Just(whateverYouWant)
}
const doc =
{ user:
{ id: '123'
, username: 'kibe'
, email: 'blabla#gmail.com'
}
, post:
{ content: 'my new post!'
, date: '20/02/2004'
}
}
coroutine(validate(doc)).bind(console.log)
With a valid doc we see -
{
id: '123',
username: 'kibe',
email: 'blabla#gmail.com',
post: 'my new post!',
date: '20/02/2004'
}
With an invalid document, otherdoc -
const otherdoc =
{ user:
{ id: '123'
, username: 'kibe'
, // <- missing email
}
, post:
{ content: 'my new post!'
, // <- missing date
}
}
coroutine(validate(otherdoc)).bind(console.log)
// no effect because validate returns a Nothing!
coroutine
Implementation of coroutine is simple and most importantly it is generic for any monad -
function coroutine (f)
{ function next (v)
{ let {done, value} = f.next(v)
return done ? value : value.bind(next)
}
return next()
}
Maybe
Lastly we supply implementation for Nothing and Just -
const Nothing =
({ bind: _ => Nothing })
const Just = v =>
({ bind: f => f(v) })
demo
Expand the snippet below to verify the results in your own browser -
const Nothing =
({ bind: _ => Nothing })
const Just = v =>
({ bind: f => f(v) })
const fromNullable = x =>
x == null
? Nothing
: Just(x)
const prop = k => t =>
fromNullable(t[k])
const props = (k, ...ks) => t =>
ks.length
? prop(k)(t).bind(props(...ks))
: prop(k)(t)
function coroutine (f)
{ function next (v)
{ let {done, value} = f.next(v)
return done ? value : value.bind(next)
}
return next()
}
function* validate (t)
{ const id = yield props("user", "id")(t)
const username = yield props("user", "username")(t)
const email = yield props("user", "email")(t)
const post = yield props("post", "content")(t)
const date = yield props("post", "date")(t)
return Just({ id, username, email, post, date })
}
const doc =
{user:{id:'123',username:'kibe',email:'blabla#gmail.com'},post:{content:'my new post!',date:'20/02/2004'}}
coroutine(validate(doc)).bind(console.log)
related reading
How do pipes and monads work together in JavaScript?
Ramda: Fold an object
How should i sort a list inside an object in functional programming with different logic based on different condition?
Sort objects in array with dynamic nested property keys
Yes you should create validateRequest and a savePostToDb Methodes which give you a boolean back. Then you can simple create two Maybe-Functions with thos two new methodes and compose it togheter. Look at my example how I would do it simple:
// Here some simple Left/Right to Just and Nothing (Maybe)-Monade function to work with
const Left = x => f => _ => f(x);
const Right = x => _ => g => g(x);
const Nothing = Left();
const Just = Right;
// your maybe-validateRequest function
const maybeValidateRequest = request =>
validateRequest(request)
? Just(user)
: Nothing
// your maybe-postToDb function
const maybePostToDb = request =>
savePostToDb(request)
? Just(request)
: Nothing
// and finally compose those two maybes and do the postRequest finally
const savePost = request =>
maybeValidateRequest(request)
(() => console.error("Validation failed"))
(_ =>
maybePostToDb(request)
(() => console.error("Save to DB failed")))
(_ => postRequest(request)) // when it's everything passed,the postRequest can be safely made

How to trouble shoot "NaN" error when the call to a function and assigning to let is the only visible issue

I'm testing an Alexa skill locally and getting an error that just says NaN. I have figured out that line let recipe = getRecipe() is the problem through console.log() statements. It doesn't appear to be in the the getRecipe() function itself because a console.log() statement at the very beginning of the try block in that function does not run, but the one at the beginning of the catch does. Thanks in advance for any suggestions.
Handler:
handle(handlerInput){
const attributes = handlerInput.attributesManager.getSessionAttributes();
const request = handlerInput.requestEnvelope.request;
switch (attributes.previousIntent){
case "FoodIntent":
if(request.intent.slots.answer.resolutions.resolutionsPerAuthority[0].values[0].value.name === 'yes'){
let randomFood = Helpers.suggestFood(handlerInput);
let queryFood = randomFood.replace(/\s+/g, '-').toLowerCase(); event
attributes.currentSuggestedFood = queryFood;
const speechText = 'Great! In the future I will be able to look up the ingredients for you.'
console.log('before call getRecipe()')
let recipe = getRecipe(handlerInput)
console.log('After call getRecipe()')
return handlerInput.responseBuilder
.speak(speechText + " "+ recipe)
.reprompt(speechText)
.withShouldEndSession(true)
.withSimpleCard('Cheer Up - YesNo', speechText)
.getResponse();
} else {
let randomFood = Helpers.suggestFood(handlerInput);
let speechText = ResponseToUsersNo[Math.floor(Math.random() * ResponseToUsersNo.length)]+
FoodPrefixes[Math.floor(Math.random() * FoodPrefixes.length)] +
randomFood + FoodSuffixes[Math.floor(Math.random() * FoodSuffixes.length)];
let repromptText = 'Did the last suggestion work for you?'
handlerInput.attributesManager.setSessionAttributes(attributes);
if (attributes.FoodsAlreadySuggested.length >= 10) {
speechText = 'I feel like you don\'t actually want anything. So I\'m leaving for now, talk to you later.'
return handlerInput.responseBuilder
.speak(speechText)
.withShouldEndSession(true)
.withSimpleCard('Cheer Up - YesNo', speechText)
.getResponse();
}
return handlerInput.responseBuilder
.speak(speechText)
.reprompt(repromptText)
.withSimpleCard('Cheer Up - YesNo', speechText)
.getResponse();
}
case "HobbyIntent":
if(request.intent.slots
And the getRecipe() function:
async function getRecipe(handlerInput) {
try{
console.log('before attributes')
const attributes = handlerInput.attributesManager.getSessionAttributes();
console.log('attributes: '+ attributes)
console.log('before url')
const url = `https://api.edamam.com/search?q=${attributes.currentSuggestedFood}&app_id=${FOOD_APP_ID}&app_key=${FOOD_APP_KEY}`; //&from=0&to=3&calories=591-722&health=alcohol-free this was on the end of the uri
console.log(url)
console.log('after url')
request.get(url, (error, response, body) => {
// let json = JSON.parse(body);
console.log('error:', error); // Print the error if one occurred
console.log('statusCode:', response && response.statusCode); // Print the response status code if a response was received
console.log('body:', body); // Print the body
//const theRecipe = await body;
const payload = JSON.parse(body)
console.log("The ingredients for "+ payload.q + " is: ")
console.log(payload.hits[0].recipe.ingredientLines)
return (payload.hits[0].recipe.ingredientLines);
});
}
catch(err){
console.log('before error statement in catch')
console.error('There was an error: ', + err)
}
};
Here is my output:
before call getRecipe()
before attributes
attributes: [object Object]
before url
https://api.edamam.com/search?q=rellenos-de-papa&app_id=b4dbea92&app_key=8d916c99b930b77c8cbb4615f0800df7
after url
before error statement in catch
There was an error: NaN
After call getRecipe()
{ version: '1.0',
response:
{ outputSpeech:
{ type: 'SSML',
ssml: '<speak>Great! In the future I will be able to look up the ingredients for you. The ingredients are [object Promise]</speak>' },
reprompt: { outputSpeech: [Object] },
shouldEndSession: true,
card:
{ type: 'Simple',
title: 'Cheer Up - YesNo',
content: 'Great! In the future I will be able to look up the
ingredients for you.' } },
userAgent: 'ask-node/2.3.0 Node/v8.12.0',
sessionAttributes:
{ foodType: 'PuertoRican',
FoodsAlreadySuggested: [ 'Platanos Maduros', 'Rellenos de Papa' ],
previousIntent: 'FoodIntent',
state: '_YES_NO',
currentSuggestedFood: 'rellenos-de-papa' } }
UPDATE:
#Shilly. So I'm still confused... An aside, I had to edit your function a bit to make the code inside the catch reachable... but anyway I think what I did still retains the core logic you were trying to impart.
My problem is that I get an error when I parse that says unexpected token o in JSON at position 1. I think this usually means I don't need to parse it because it's already a valid js object. Cool. So I remove the parse, but then I get Cannot read property '0' of undefined., referring of course to my return payload.hits[0].recipe.ingredientLines. Can't seem to wrap my head around why. Thanks a bunch for your help.
function getRecipe(handlerInput) {
const attributes = handlerInput.attributesManager.getSessionAttributes();
const url = `https://api.edamam.com/search?q=${attributes.currentSuggestedFood}&app_id=${FOOD_APP_ID}&app_key=${FOOD_APP_KEY}`; //&from=0&to=3&calories=591-722&health=alcohol-free this was on the end of the uri
return get( url, ( response, body ) => {
const payload = JSON.parse(body)
console.log(payload)
return payload.hits[0].recipe.ingredientLines;
}).catch( error => {
console.error( `failed GET request for: ${ url }` );
console.error( error );
});
};
Also here is the beginning of the body in the response, which doesn't look parsed to me... body: '{\n "q" : "tostones",\n "from" : 0,\n "to" : 10,\n "params" : {\n
Finally figured it out. Many thanks to #Shilly for guiding me in the proper direction. My understanding of async and await was wrong. These sources were helpful:
Returning handler.ResponseBuilder from promise.then() method
https://medium.com/#tkssharma/writing-neat-asynchronous-node-js-code-with-promises-async-await-fa8d8b0bcd7c
Here is my updated code:
The async handler relies on a function that I created to use Promises with #Shilly's help. It's probably not the most concise way, but it works!
Handler:
async handle(handlerInput){
const attributes = handlerInput.attributesManager.getSessionAttributes();
const request = handlerInput.requestEnvelope.request;
switch (attributes.previousIntent){
case "FoodIntent":
if(request.intent.slots.answer.resolutions.resolutionsPerAuthority[0].values[0].value.name === 'yes'){
let randomFood = Helpers.suggestFood(handlerInput);
let queryFood = randomFood.replace(/\s+/g, '-').toLowerCase();
attributes.currentSuggestedFood = queryFood;
const speechText = 'Great! Here are the ingredients!'
let recipe = await getRecipe(handlerInput)
let recipeIngredients = recipe.hits[0].recipe.ingredientLines;
return handlerInput.responseBuilder
.speak(speechText+ 'The ingredients are '+ recipeIngredients)
.reprompt(speechText)
.withShouldEndSession(true)
.withSimpleCard('Cheer Up - YesIntentFood', recipeIngredients)
.getResponse();
function:
async function getRecipe(handlerInput) {
const attributes = handlerInput.attributesManager.getSessionAttributes();
const url = `https://api.edamam.com/search?q=${attributes.currentSuggestedFood}&app_id=${FOOD_APP_ID}&app_key=${FOOD_APP_KEY}`;
console.log(url)
return new Promise (function(resolve, reject) {
request.get(url, (error, response, body) => {
if (error) {
reject(error);
} else {
resolve(JSON.parse(body))
}
});
})
};
Output:
https://api.edamam.com/search?q=pernil&app_id=b4dbea92&app_key=8d916c99b930b77c8cbb4615f0800df7
{ version: '1.0',
response:
{ outputSpeech:
{ type: 'SSML',
ssml: '<speak>Great! In the future I will be able to look up the ingredients for you.The ingredients are 2 1/2 pounds pork shoulder, boston butt, pernil,2 garlic cloves,1 small onion,1 bunch cilantro,1 jalapeƱo,1 cup orange juice,1 cup pineapple juice,1 lemon,Handfuls salt,Pepper to taste,Ground cumin</speak>' },
reprompt: { outputSpeech: [Object] },
shouldEndSession: true,
card:
{ type: 'Simple',
title: 'Cheer Up - YesIntentFood',
content: [Array] } },
userAgent: 'ask-node/2.3.0 Node/v8.12.0',
sessionAttributes:
{ foodType: 'PuertoRican',
FoodsAlreadySuggested: [ 'Platanos Maduros', 'Pernil' ],
previousIntent: 'FoodIntent',
state: '_YES_NO',
currentSuggestedFood: 'pernil' } }
const attributes = handlerInput.attributesManager.getSessionAttributes() throws an error NaN for reasons still unknown.
The catch() handler catches this error and logs it.
But since the function is async, and you don't resolve the promise inside the catch clause, you get this [object Promise] stringified version of that promise instead of the actual ingredient list.
Edit:
Have you considered using util.promisify() so you don't have to mix callbacks and promises?
const { promisify } = require( 'util' );
const request = require( 'request' );
const get = promisify( request.get );
function getRecipe(handlerInput) {
const attributes = handlerInput.attributesManager.getSessionAttributes();
const url = `https://api.edamam.com/search?q=${attributes.currentSuggestedFood}&app_id=${FOOD_APP_ID}&app_key=${FOOD_APP_KEY}`; //&from=0&to=3&calories=591-722&health=alcohol-free this was on the end of the uri
return get( url, ( response, body ) => {
const payload = JSON.parse(body)
return payload.hits[0].recipe.ingredientLines;
}).catch( error ) {
console.error( `failed GET request for: ${ url }` );
console.error( error );
});
};
Same can be written with async/await style, but I'm not fluent enough in it to get it 100% correct without being able to test the code myself.

Categories

Resources