In my project I have a function for downloading the files. When click the button the function onDownload will be called:
import {useOnDownload} from "../../use/useOnDownload"
setup() {
...
const loading = ref(null)
onDownload = (id) => {
loading.value = id
await useOnDownload(id)
loading.value = null
}
return {loading, onDownload}
}
I refactored the code for api in a file useOnDownload.js call because the same code is used in another components as well.
export async function useOnDownload(id) {
// make api call to server with axios
}
What I did wrong? I need to wait for the function useOnDownload ... in order the loader to work.
Here is how to make async composable functions with async await syntax
export default function useOnDownload() {
const isLoading = ref(true);
const onDownload = async () => {
isLoading.value = true;
try {
const { data } = await axios.post('/api/download', {id: id},
{responseType: 'blob'})
// handle the response
} catch (error) {
console.log(error);
} finally {
isLoading.value = false;
}
};
// invoke the function
onDownload();
return { // return your reactive data here };
}
import useOnDownload from "../../use/useOnDownload"
// no await in setup script or function
const { reactiveDataReturned } = useOnDownload();
Read more here
onDownload must be async in order to use await within it
I managed to solved another way without async and await...
I passed the reference object loader to the function parameter (as optional) and handle from there...
export function useOnDownload(id, loader) {
if(loader !== undefined) {
loader.value = id
}
axios.post('/api/download', {id: id}, {
responseType: 'blob'
}).then(response => {
// handle the response
...
if(loader !== undefined) {
loader.value = null
}
}).catch(error => {
// handle the error
...
if(loader !== undefined) {
loader.value = null
}
})
}
You are using the await keyword in your onDownlaod function, but the function is not asynchronous. Here's how you should update it.
// next line important
onDownload = async(id) => {
loading.value = id
await useOnDownload(id)
loading.value = null
}
Related
in my react native components I call async functions defined inside my mobx stores, however when I call them the code after them inmediatly runs instead of waiting for the async function to end.
In my case "ACTION COMPLETED" appears on console before the axios call is completed...
I call it like this inside my components:
const sendVerify = async () =>
{
try
{
//Tried with await
await root.userStore.getLoggedUser(response.data.user.id);
console.log('ACTION COMPLETED');
//Also tried with then at didn't work
root.userStore.getLoggedUser().then(response => console.log('ACTION COMPLETED'));
}
catch (error)
{
throw error;
}
};
Async function inside UserStore:
export class UserStore
{
root = null;
constructor(root)
{
makeAutoObservable(this, { root: false });
this.root = root;
}
async getLoggedUser(user_id = false)
{
try
{
let url = env.BASE_URL+'/api/auth/sanctum/user';
let params = {};
let response = await http.get(url, {params:params});
return true;
}
catch (error)
{
return false;
throw error;
}
}
Usually I do useEffect cleanups like this:
useEffect(() => {
if (!openModal) {
let controller = new AbortController();
const getEvents = async () => {
try {
const response = await fetch(`/api/groups/`, {
signal: controller.signal,
});
const jsonData = await response.json();
setGroupEvents(jsonData);
controller = null;
} catch (err) {
console.error(err.message);
}
};
getEvents();
return () => controller?.abort();
}
}, [openModal]);
But I don't know how to do in this situation:
I have useEffect in Events.js file that get events from function and function in helpers.js file that create events on given dates except holidays (holiday dates fetch from database).
Events.js
useEffect(() => {
if (groupEvents.length > 0) {
const getGroupEvents = async () => {
const passed = await passEvents(groupEvents); // function in helpers.js (return array of events)
if (passed) {
setEvents(passed.concat(userEvents));
} else {
setEvents(userEvents);
}
};
getGroupEvents();
}
}, [groupEvents, userEvents]);
helpers.js
const passEvents = async (e) => {
try {
const response = await fetch(`/api/misc/holidays`, {
credentials: 'same-origin',
});
const jsonData = await response.json();
const holidays = jsonData.map((x) => x.date.split('T')[0]); // holiday dates
return getEvents(e, holidays); // create events
} catch (err) {
console.error(err.message);
}
};
You can either not clean up, which really is also fine in many situations, but if you definitely want to be able to abort the in-flight request, you will need to create the signal from the top-level where you want to be able to abort, and pass it down to every function.
This means adding a signal parameter to passEvents.
Remove ?:
return () => controller.abort();
Like to this: https://www.youtube.com/watch?v=aKOQtGLT-Yk&list=PL4cUxeGkcC9gZD-Tvwfod2gaISzfRiP9d&index=25
Is it what you want?
I have my App component defined as below;
function App() {
const [state1, setState1] = useState({});
const [state2, setState2] = useState({});
const [isApiCallDone, setIsApiCallDone] = useState(false);
const fetchFn = useMyCustomFetch();
useEffect(() => {
(async function() {
try {
let [state11] = await fetchFn('api/api1', {}, 'GET');
let [state22] = await fetchFn('api/api2', {}, 'GET');
setState1(state11); // Is there a better way to set this ?
setState2(state22);
setIsApiCallDone(true);
} catch (e) {
console.error(e);
}
})();
}, []);
useEffect(() => {
if (Object.keys(state1).length > 0 && Object.keys(state2).length > 0) {
// Set some other state variables on App
}
}, [state1, state2])
return (
<>
<MyContextProvider>
{isApiCallDone && (
<MyComponent />
)
}
</MyContextProvider>
</>
}
Also my useMyCustomFetch hook looks like below
export default function useMyCustomFetch() {
const fetchData = async (url, reqData, reqType) => {
try {
var statusObj = {
statMsg: null
};
const response = await fetch(url, reqOptions);
if (!response.ok) {
throw response;
}
statusObj.status = "success";
const json = await response.json();
return [json, statusObj];
} catch (error) {
statusObj.status = "error";
return [null, statusObj];
}
}
return fetchData;
}
My questions are;
For the lines
let [state11] = await fetchFn('api/api1', {}, 'GET');
setState1(state11);
I first assign it to a new variable state11 and then assign the same by calling setState1.
Is there a better way to set the state1 directly?
Is the usage of async function inside the useEffect fine ?
For Question 1:
You can directly setState like below without using state11.
useEffect(() => {
(async function () {
try {
setState1(
(await fetchFn("https://reqres.in/api/users/1", {}, "GET"))[0]
); // Is there a better way to set this ?
setState2(
(await fetchFn("https://reqres.in/api/users/2", {}, "GET"))[0]
);
setIsApiCallDone(true);
} catch (e) {
console.error(e);
}
})();
}, []);
For Question 2:
I don't see any problem using async & IIFE in the useEffect. In fact I like the way its done. Looks good to me.
Please find screenshot of the state being set properly in the console (I have used a dummy api url):
If you don't want to use async functions, you can use the Promise.prototype.then() method to combine your calls like this :
useEffect(() => {
fetchFn('api/api1', {}, 'GET').then(state => {
setState1(state[0]);
return fetchFn('api/api2', {}, 'GET')
}).then(state => {
setState2(state[0]);
setIsApiCallDone(true);
}).catch(console.log);
}, []);
An other way to set this with an async function but more factorised is this way :
useEffect(() => {
(async function() {
try {
await fetchFn('api/api1', {}, 'GET')
.then(tab => tab[0])
.then(setState1);
await fetchFn('api/api2', {}, 'GET');
.then(tab => tab[0])
.then(setState2);
setIsApiCallDone(true);
} catch (e) {
console.error(e);
}
})();
}, []);
Finally, the usage of the async function in an useEffect is not a problem.
Thanks for the help you have given me...
I'm trying to call a constant on view since it's supposed to return a value. In this case in the method, there is a function that returns me a string. That later I'm going to send him to another sight for his call.
I have a view that I call 2 times because I try to print 2 carousels with different information.
<app-carousel v-if="trendingMovies" :title="$t('home.new_movies')" :view-all-url="MoviesUrl" :data="trendingMovies"></app-carousel>
<app-carousel v-if="trendingSeries" :title="$t('home.new_tv_series')" :view-all-url="SeriesUrl" :data="trendingSeries"></app-carousel>
In the export default I have this:
async asyncData () {
try {
const trendingMovies = await this.treding('movies');
const trendingSeries = await this.treding('series');
return { trendingMovies, trendingSeries };
} catch {
console.log(404);
}
},
methods: {
async treding(media) {
let { data } = await axios.get('/api/v1/get/type/' + media);
return await data.list;
},
}
What I am trying to do is store its respective string in each constant and then pass it to the carousel view.
The problem is that I always get the following error in the <app-carousel> tags.
You can try refactoring the asyncData with the following approach:
async asyncData () {
let trendingMovies = null;
let trendingSeries = null;
try {
trendingMovies = await this.treding('movies');
trendingSeries = await this.treding('series');
} catch {
console.log(404);
}
return { trendingMovies, trendingSeries };
},
This way, it makes sure that asyncData always returns the correct data object.
If it's nuxt, then the answer from the documentation:
Warning: You don't have access to the component instance through this inside asyncData because it is called before initiating the component.
You need to make a method not in Vue. Something like:
async function treding(media) {
const { data } = await axios.get('/api/v1/get/type/' + media);
return data.list;
}
export default {
async asyncData(context) {
try {
const promises = [treding('movies'), treding('series')];
const [trendingMovies, trendingSeries] = await Promise.all(promises);
return { trendingMovies, trendingSeries };
} catch {
console.log(404);
return { trendingMovies: null, trendingSeries: null };
}
}
}
Or you can don't use asyncData
data() {
return {
trendingMovies: null,
trendingSeries: null,
};
},
async created() {
try {
const promises = [this.treding('movies'), this.treding('series')];
const [trendingMovies, trendingSeries] = await Promise.all(promises);
this.trendingMovies = trendingMovies;
this.trendingSeries = trendingSeries;
catch {
console.log(404);
}
},
methods: {
async treding(media) {
const { data } = await axios.get('/api/v1/get/type/' + media);
return data.list;
},
},
I have a file asyncAwait.js that has a simple function:
async function doStuff() {
return(`Function returned string.`);
}
module.exports.doStuff = doStuff;
In another module, testing.js, I invoke and all works as expected:
var functions = require(`./functions`);
(async () => {
const test = await functions.asyncAwait.doStuff();
console.log(test);
})();
This logs "Function returned string." to the console.
All good.
However, if I use axios in asyncAwait.js:
const axios = require(`axios`);
async function doStuff(parameter) {
const url = `https://jsonplaceholder.typicode.com/posts/1`;
const getData = async url => {
try {
const response = await axios.get(url);
const data = response.data;
console.log(data);
} catch (error) {
console.log(error);
}
};
return(getData(url));
}
module.exports.doStuff = doStuff;
Then in testing.js:
var functions = require(`./functions`);
(async () => {
const test = await functions.asyncAwait.doStuff();
console.log(test);
})();
This logs undefined.
Why does the function call return undefined in the second example?
In your example getData has no return. In this case your function will implicitly return undefined. To fix it you could change that function to the following:
const getData = async url => {
try {
const response = await axios.get(url);
return response.data;
} catch (error) {
return error
}
};
module.exports.doStuff = doStuff;
May I suggest you :
module.exports=doStuff;
Or
exports.doStuff
and maybe but not sure what you're trying to achieve
replace
return(getData(url));
by
return(()=>{return getData(url)});