i have a skill and i want to load some data from an url and store it in the SessionAttributes.
so i wrote this into my handle(handlerInput)of my LaunchRequestHandler:
require('https').get(url, (resp) => {
resp.on('data', (chunk) => {
data += chunk;
});
resp.on('end', () => {
let attributes = JSON.parse(data);
console.log(attributes);
handlerInput.attributesManager.setSessionAttributes(attributes);
});
});
the log shows me the correct object, but when i try to load the sessionAttributes in the next intent it's empty. I Assume it has something to do with the setSessionAttributes being in the response function, because if i set something directly after this code, it works. Any ideas?
This might be because of the asynchronous operation. Please use async/await to make the API call and then save. Sample example,
const getData = () => {
return new Promise((resolve, reject) => {
require("https").get(url, resp => {
resp.on("data", chunk => {
data += chunk;
});
resp.on("end", () => {
resolve(data);
});
});
});
};
const LaunchRequestHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === "LaunchRequest";
},
async handle(handlerInput) {
const speechText = "welcome";
const data = await getData(); //asynchronous operation
const sessionAttributes = handlerInput.attributesManager.getSessionAttributes();
sessionAttributes.data = data;
handlerInput.attributesManager.setSessionAttributes(sessionAttributes);
return handlerInput.responseBuilder
.speak(speechText)
.reprompt(speechText)
.withSimpleCard("Welcome to the Skill", speechText)
.getResponse();
}
};
Related
I am trying to fetch data from Algolia database (index.search is similar to fetch) in useEffect,but then I find the order it execute is not the way I think.I console "queryNews1", "queryNews2", ..."queryNews6" in async function queryNews(), and I think they will sequentially print out in console(see image below). But I find that after queryNews2, it "jump out" of queryNews() but execute the code outside queryNews(), after console.log("5"), it go back to execute "queryNews3".
I guess it's an asychronous issue, so I wrap queryNews() inside an another async function const getData = async () => { await queryNews(keyword); }; and call getData(), but it's still execute in wrong way.Why and does anybody know how to fix that??
Sorry for my bad English!
mobile in the console is writing in articleState.map(() => { console.log("mobile"); return (); });
console results image
const [articleState, setArticles] = useState<ArticleType[]>([]);
useEffect(() => {
console.log("1");
if (windowResized === "large" || windowResized === undefined) return;
let isFetching = false;
let isPaging = true;
let paging = 0;
console.log("2");
async function queryNews(input: string) {
console.log("queryNews1");
isFetching = true;
setIsLoading(true);
setSearchState(true);
setPageOnLoad(true);
console.log("queryNews2");
const resp = await index.search(`${input}`, {
page: paging,
});
console.log("queryNews3");
const hits = resp?.hits as ArticleType[];
setTotalArticle(resp?.nbHits);
console.log("queryNews4");
setArticles((prev) => [...prev, ...hits]);
console.log("queryNews5");
setIsLoading(false);
console.log("queryNews6");
paging = paging + 1;
if (paging === resp?.nbPages) {
isPaging = false;
setScrolling(true);
return;
}
console.log("queryNews7");
isFetching = false;
setSearchState(false);
setPageOnLoad(false);
console.log("queryNews8");
}
console.log("3");
async function scrollHandler(e: WheelEvent) {
if (window.innerHeight + window.scrollY >=
document.body.offsetHeight - 100) {
if (isFetching || !isPaging) return;
console.log("scrollHandler");
getData();
}
}
const getData = async () => {
await queryNews(keyword);
};
getData()
console.log("4");
window.addEventListener("wheel", scrollHandler);
console.log("5");
return () => {
window.removeEventListener("wheel", scrollHandler);
};
}, [keyword, setSearchState, windowResized]);
Thanks to kim3er, that really help.
But simillar situation happened again when I scroll, it console.log("queryNews2"), and then it console "mobile", which means it render the component again, and then go back to queryNews() to finish execute the rest of the function?Why didn't it wait while I already put all the code in
getData().then(() => {
console.log("6");
window.addEventListener("wheel", scrollHandler);
console.log("7");
});
Thanks!!
(stack overflow suddenly said I can't embed image now so I paste an image link instead)
https://imgur.com/a/lDKEzxy
useEffect(() => {
console.log("1");
if (windowResized === "large" || windowResized === undefined) return;
let isFetching = false;
let isPaging = true;
let paging = 0;
console.log("2");
async function queryNews(input: string) {
console.log("queryNews1");
isFetching = true;
setIsLoading(true);
setSearchState(true);
setPageOnLoad(true);
console.log("queryNews2");
const resp = await index.search(`${input}`, {
page: paging,
});
console.log("queryNews3");
const hits = resp?.hits as ArticleType[];
setTotalArticle(resp?.nbHits);
console.log("queryNews4");
setArticles((prev) => [...prev, ...hits]);
console.log("queryNews5");
setIsLoading(false);
console.log("queryNews6");
paging = paging + 1;
if (paging === resp?.nbPages) {
isPaging = false;
setScrolling(true);
return;
}
console.log("queryNews7");
isFetching = false;
setSearchState(false);
setPageOnLoad(false);
console.log("queryNews8");
}
console.log("3");
async function scrollHandler(e: WheelEvent) {
if (
window.innerHeight + window.scrollY >=
document.body.offsetHeight - 100
) {
if (isFetching || !isPaging) return;
console.log("scrollHandler");
getData().then(() => {
console.log("6");
window.addEventListener("wheel", scrollHandler);
console.log("7");
});
}
}
const getData = async () => {
await queryNews(keyword);
};
getData().then(() => {
console.log("4");
window.addEventListener("wheel", scrollHandler);
console.log("5");
});
return () => {
window.removeEventListener("wheel", scrollHandler);
};
}, [keyword, setSearchState, windowResized]);
You're calling getData() without awaiting it. Because of this, it'll run parallel to the top level useEffect code. This isn't an issue, if getData() is the last line of code in the function.
But if you do need that initial getData() to complete before hitting console.log("4");, you could do this:
getData()
.then(() => {
console.log("4");
window.addEventListener("wheel", scrollHandler);
console.log("5");
});
From console.log("4"); will run after the call to getData().
Clarifier on async and .then()
With this function in mind:
const doSomething = async () => {
// Do something async
console.log('during');
});
The following:
const asyncFunc = async () => {
console.log('before');
await doSomething();
console.log('after');
});
Is equivalent to:
const asyncFunc = () => {
console.log('before');
doSomething()
.then(() => {
console.log('after');
});
});
Either will return:
before
during
after
However, if you used:
const syncFunc =() => {
console.log('before');
doSomething();
console.log('after');
});
Becasue I have not awaited doSomething(), I have created a race condition, whereby during and after could be returned in a different order depending on how long it took the async code to complete. Because the syncFunc script will continue running as soon as doSomething has been called (but crucially, has not finished).
So you could get:
before
after
during
Wrapping await queryNews(keyword); in another function called getData() does not make the function synchronous, it just means that you don't have to keep typing in the keyword parameter. You still need to await getData(), in order to ensure completion before continuing.
#kim3er
Thank you for your detailed answer, and sorry for my late response.
According to your explanation about difference between async/await and .then(),
I found that I didn't await getData() in scrollHandler(), so I modified my code again, I call queryNews(keyword) this time instead, but then something weird still exist.
The console.log sequence is right when first load(No1~10 in image below),and then when I scroll, I call scrollHandler(), and inside scrollHandler, await queryNews() is executed, but then again,it console to "queryNews2", and then it "jump out" of queryNews() to render component again I guess, because the word "mobile" in JSX tag is console before "queryNews3"(No13~18 in image below).
I use await queryNews(keyword) in scollHandler() this time, but it still has wrong console sequence.Why?Does it has anything to do with setState inside queryNews()?Because as far as I know, setState will trigger a component re-render?
Thank you for answer my question patiently.
console image
useEffect(() => {
console.log("1");
if (windowResized === "large" || windowResized === undefined) return;
let isFetching = false;
let isPaging = true;
let paging = 0;
console.log("2");
async function queryNews(input: string) {
console.log("queryNews1");
isFetching = true;
setIsLoading(true);
setSearchState(true);
setPageOnLoad(true);
console.log("queryNews2");
const resp = await index.search(`${input}`, {
page: paging,
});
console.log("queryNews3");
const hits = resp?.hits as ArticleType[];
setTotalArticle(resp?.nbHits);
console.log("queryNews4");
setArticles((prev) => [...prev, ...hits]);
console.log("queryNews5");
setIsLoading(false);
console.log("queryNews6");
paging = paging + 1;
if (paging === resp?.nbPages) {
isPaging = false;
setScrolling(true);
return;
}
console.log("queryNews7");
isFetching = false;
setSearchState(false);
setPageOnLoad(false);
console.log("queryNews8");
}
console.log("3");
async function scrollHandler(e: WheelEvent) {
if (
window.innerHeight + window.scrollY >=
document.body.offsetHeight - 100
) {
if (isFetching || !isPaging) return;
console.log("scrollHandler");
await queryNews(keyword);
console.log("end scrollHandler");
}
}
queryNews(keyword).then(() => {
console.log("4");
window.addEventListener("wheel", scrollHandler);
console.log("5");
});
return () => {
window.removeEventListener("wheel", scrollHandler);
};
}, [keyword, setSearchState, windowResized]);
I am using REDUX-TOOLKIT-QUERY, Now I have a situation, I have to call one mutation. once that mutation returns a response, and I have to use that response in the other three mutations as a request parameter. How can we implement this in a proper way?
const [getSettings, { isLoading, data: Settings }] =useSettingsMutation();
const [getMenu] =useMenuMutation();
const [getServices] = useServicesMutation();
useEffect(() => {
const obj = {
Name: "someNme",
};
if (obj) getSettings(obj);
if (Settings?._id ) {
const id = settings._id;
getServices(id);
getMenu(id);
getServices(id);
}
}, []);
useMutation will return a promise, you can directly await this promise to get the return result:
const [getSettings, { isLoading, data: Settings }] = useSettingsMutation()
const [getMenu] = useMenuMutation()
const [getServices] = useServicesMutation()
useEffect(() => {
;(async () => {
try {
const obj = {
Name: 'someNme',
}
const data = await getSettings(obj).unwrap()
if (data._id !== undefined) {
const id = data._id
getServices(id)
getMenu(id)
getServices(id)
// ...
}
} catch (err) {
// ...
}
})()
}, [])
I'm looking for a solution/module where I don't need to inject inital/fallback data for swr/react-query things from getServerSideProps. Like...
from
// fetcher.ts
export default fetcher = async (url: string) => {
return await fetch(url)
.then(res => res.json())
}
// getUserData.ts
export default function getUserData() {
return fetcher('/api')
}
// index.tsx
const Page = (props: {
// I know this typing doesn't work, only to deliver my intention
userData: Awaited<ReturnType<typeof getServerSideProps>>['props']
}) => {
const { data } = useSWR('/api', fetcher, {
fallbackData: props.userData,
})
// ...SSR with data...
}
export const getServerSideProps = async (ctx: ...) => {
const userData = await getUserData()
return {
props: {
userData,
},
}
}
to
// useUserData.ts
const fetcher = async (url: string) => {
return await fetch(url)
.then(res => res.json())
};
const url = '/api';
function useUserData() {
let fallbackData: Awaited<ReturnType<typeof fetcher>>;
if (typeof window === 'undefined') {
fallbackData = await fetcher(url);
}
const data = useSWR(
url,
fetcher,
{
fallbackData: fallbackData!,
}
);
return data;
}
// index.tsx
const Page = () => {
const data = useUserData()
// ...SSR with data...
}
My goal is making things related to userData modularized into a component.
I have a function that create tasks and writing it in firebase real time database.
export const createNewTask = (task) => new Promise(async (resolve, reject) => {
try {
await database().ref('tasks').child(auth().currentUser.uid).child(task.taskCreationDate.toString()).set(task);
resolve();
} catch (e) {
reject(e);
}
});
And it's working good :
Now, I have a socond functions that should read that tasks.
export const fetchTasks = () => (dispatch) => new Promise(async (resolve, reject) => {
try {
const snapshot = await database().ref('tasks').child(auth().currentUser.uid).once('value');
if (snapshot.exists) {
const tasks = snapshot.val();
dispatch({
type: FETCH_TASKS,
payload: tasks,
});
resolve(tasks);
} else {
resolve(snapshot);
}
} catch (e) {
reject(e);
}
});
And here is the problem:
When I'm using useSelector in my home screen const tasks = useSelector(state => state.GeneralReducer.taskList);
The tasks list is undefiend.
When I used mock data with same objects , it's work fine.
How can I get a list of task?
Ok , fixed it by adding :
Object.keys(data).map(key => ({...data[key], id: key}));
to the function fetchTasks.
export const fetchTasks = () => (dispatch) => new Promise(async (resolve, reject) => {
try {
const snapshot = await database().ref('tasks').child(auth().currentUser.uid).once('value');
if (snapshot.exists) {
const data = snapshot.val();
const tasks = Object.keys(data).map(key => ({...data[key], id: key}));
dispatch({
type: FETCH_TASKS,
payload: tasks,
});
resolve(tasks);
} else {
resolve(snapshot);
}
} catch (e) {
reject(e);
}
});
I have a Redux action which needs to make 2 subsequent ajax calls.
The first calls googlemaps api: https://maps.googleapis.com/maps/api/geocode/json?address=${searchTerm}&key=${gmapsKey}
The second calls a local service based on those results
/api/content/stores/byDistance/${lat},${lng}/sort
I'm using superagent to do the ajax calls. Clearly I'm experience difficulties keeping track of the promises, and including failures.
Am I mis-undestanding a core concept of Promises? Is there a simpler way to write the below?
export function loadBySearch(searchTerm) {
const geoSearchUrl = `https://maps.googleapis.com/maps/api/geocode/json?address=${searchTerm}&key=${gmapsKey}`;
return {
types: [LOAD, LOAD_BY_LAT_LONG, LOAD_FAIL],
//Do I need to make this promise here?
promise: (client) => {
const promise = new Promise( (resolve, reject) => {
console.info('making google geocode request', geoSearchUrl);
superagent.get(geoSearchUrl)
.set('Accept', 'application/json')
.then( (successData1) =>{
const results = successData1.body.results;
if (!results.length) {
reject(`no results found for this search : ${searchTerm}`);
return;
}
const lat = results[0].geometry.location.lat;
const lng = results[0].geometry.location.lng;
const path = `/api/content/stores/byDistance/${lat},${lng}/sort`;
client.get(path).then(
(successData2) => {
resolve( {
searchTerm: searchTerm,
searchLocation: {
lat,
lng
},
data: successData2
});
},
(errorData2) => {
reject( {
searchTerm: searchTerm,
result: errorData2
});
},
);
},
(errorData1) => {
reject({
searchTerm: searchTerm,
result: errorData1
});
}
);
});
return promise;
}
};
}
I'm not using superagent, but I'm guessing something like this might just work:
superagent.get(geoSearchUrl)
.set('Accept', 'application/json')
.then(successData1 => {
const results = successData1.body.results;
if (!results.length) {
throw(`no results found for this search : ${searchTerm}`);
}
return Promise.resolve(results);
})
.then(results => {
const lat = results[0].geometry.location.lat;
const lng = results[0].geometry.location.lng;
const path = `/api/content/stores/byDistance/${lat},${lng}/sort`;
return client.get(path);
})
.then(successData2 => {
return Promise.resolve({
searchTerm: searchTerm,
searchLocation: {
lat,
lng
},
data: successData2
});
})
.catch(error => {
return Promise.reject({
searchTerm: searchTerm,
result: error
});
});
Haven't test it, but I hope at least it helps ;)