Cannot find a way to wait for vue.js async function - javascript

I have the following code that works when I uncomment the "return" in fetchData below. How can I set it up so I wait for this.fetchData to finish to fill items? I have tried variants of other promises, async, await, but cannot get it to actually work...
So far I set a this.items inside fetchData but I realize it's absolutely not what I want to actually do. It shows me the first X lines of my call, because of setting it this way, but all the code that is supposed to happen after the fetchData call is bugging, starting with trying to do items.length.
import axios from 'axios';
new Vue({
el: '#app',
data() {
return {
search: '',
totalItems: 0,
items: [],
loading: true,
pagination: {},
headers: [ //some headers
],
items: []
}
},
watch: {
pagination: {
handler () {
this.getDataFromApi()
.then(data => {
this.items = data.items
this.totalItems = data.total
})
},
deep: true
}
},
mounted () {
this.getDataFromApi()
.then(data => {
this.items = data.items
this.totalItems = data.total
})
},
methods: {
fetchData() {
axios.get('/api/v1/translations').then(response => {
//console.log(response.data)
this.items = response.data.data
})
//the return below is working 100%:
//return [{id:1,key:1,lg:'en',text:'woopie'}];
},
getDataFromApi () {
this.loading = true
return new Promise((resolve, reject) => {
const { sortBy, descending, page, rowsPerPage } = this.pagination
let items = this.fetchData()
const total = items.length
//some code to setup pagination and sort
setTimeout(() => {
this.loading = false
resolve({
items,
total
})
}, 1000)
})
}
},
})

Updated answer:
Apologies, in my original answer below I completely missed this:
So far I set a this.items inside fetchData but I realize it's absolutely not what I want to actually do.
My answer assumed you did want this.items. Sorry about that.
To just get the info from fetchData without using this.items, use the then handler on the promise from axios.get to return a new promise (remember, then and catch return promises) that resolves with response.data.data:
fetchData() {
// vvvvvv------------------------
return axios.get('/api/v1/translations').then(response => {
return response.data.data;
// ^^^^^^^^^^^^^^^^^^^^^^^^^
})
},
This can be written more succinctly:
fetchData() {
return axios.get('/api/v1/translations').then(response => response.data.data);
},
Then, in getDataFromApi, use a then handler on that promise:
getDataFromApi () {
this.loading = true
return this.fetchData().then(items => {
// You might use this at some stage: const { sortBy, descending, page, rowsPerPage } = this.pagination
this.loading = false;
return { items, total: items.length };
});
}
Note that it's important that the consumer of getDataFromApi handles promise rejection (e.g., uses a catch handler on it). If you don't want the consumer to do that, don't return the promise, and use a catch within getDataFromApi instead.
Original answer:
You return the promise from axios.get:
fetchData() {
// vvvvvv------------------------
return axios.get('/api/v1/translations').then(response => {
//console.log(response.data)
this.items = response.data.data
})
},
...and then use it rather than creating a new one in getFromApi:
getDataFromApi () {
this.loading = true
return this.fetchData().then(() => {
// You might use this at some stage: const { sortBy, descending, page, rowsPerPage } = this.pagination
this.loading = false;
return { items: this.items, total: this.items.length };
});
}
Remember that then (and catch) create new promises; there's no reason for new Promise if you already have a promise to work with. More on that part: What is the explicit promise construction antipattern and how do I avoid it?
(Note that I'm assuming you want this.items on the instance for some reason. If you didn't, you'd just want to use response.data as the return value of your axios.get...then callback and use it in getDataFromApi.)

Related

Variable Content Disappears

I have a variable called dataShops created at the root of the function.
const fetchSales = () => {
const dateInitial = moment(dayInitial, 'ddd, D MMM YYYY').format('YYYY-MM-DD')
const dateFinal = moment(dayFinal, 'ddd, D MMM YYYY').format('YYYY-MM-DD')
let dataShops = []
try {
setIsLoading(true)
app.currentUser.customData.lojas.forEach(async (value, index) => {
const mongo = app.currentUser.mongoClient("mongodb-atlas").db(value.base)
const data = await mongo.collection("vendas").find({
data: {
$gte: dateInitial,
$lte: dateFinal
}
}, { sort: { data: 1, hora: 1 } })
dataShops = dataShops.concat(data)
console.log(dataShops)//the data appears here
})
if (mounted) {
console.log(dataShops) //at this point just an empty array
setDataMain(dataShops)
}
} catch (e) {
setError(e)
} finally {
setRefreshing(false)
setIsLoading(false)
}
}
I fill it inside a forEach and inside I can see its contents. But outside is just an empty array. Why?
This happens because the callback function provided to forEach is an async function. That means the control flows to the following statement before the loop runs.
Once the fetchSales function finishes executing (which includes logging an empty array), the async functions in your for-each loop are fetched from the event queue and processed.
The easiest way to get around this is to use a regular for loop instead of for-each. This would however require you to declare the fetchSales method as async.
Another option is to create an array of promises instead of using a forEach loop, and the waiting for all promises to resolve using Promise.all before accessing the values:
let dataShops = []
try {
setIsLoading(true);
const promises = app.currentUser.customData.lojas.map((value) => {
return new Promise(async (resolve) => {
const mongo = app.currentUser.mongoClient("mongodb-atlas").db(value.base);
const data = await mongo.collection("vendas").find(
{
data: {
$gte: dateInitial,
$lte: dateFinal,
},
},
{ sort: { data: 1, hora: 1 } }
);
dataShops = dataShops.concat(data);
resolve();
});
});
Promise.all(promises).then(() => {
if (mounted) {
setDataMain(dataShops);
}
setRefreshing(false);
setIsLoading(false);
});
} catch (e) {
setError(e);
}

promise keep return the empty array

constructor() {
super();
this.state = {
detailWallet: [],
user: [],
historia: [],
namaPertama: [],
namaKirim: [],
}
componentDidMount() {
this.getDetailAPI();
this.getDetailPlaceAPI();
this.getHistoria();
}
getNameAPI = () => {
const id = this.props.match.params.id;
return new Promise(resolve => {
axios.get(`/wallet/user/${id}`)
.then((result) => {
resolve(
this.setState({
namaPertama: result.data.data.content[0].user.firstName
})
)
})
});
}
getHistoria = () => {
console.log(this.getNameAPI())
Promise.all([this.getNameAPI()])
.then(([result]) => {
this.setState({
namaKirim: result
})
})
axios.get(`/wallet/type/6?q=`)
.then((result) => {
this.setState({
historia: result.data.data.content
})
})
}
so i make a function that have a method get inside a promise, and when i console.log it in getHistoria, the promise give me an empty array..
can someone tell me what's wrong with my code? i just learning about the promise so i still don't really know about it, thank you..
You are returning the result of setState, I don't think setState is returning anything. Besides, setState is asynchronous, so you won't have the result of the updated state if you loged the state just after.
Just set the state with setState and return some value.
EDIT:
getNameAPI = () => {
const id = this.props.match.params.id;
return new Promise(resolve => {
axios.get(`/wallet/user/${id}`)
.then((result) => {
let value = result.data.data.content[0].user.firstName;
this.setState({ namaPertama: value})
resolve(value)
})
});
}

I can't seem to figure out why the third function runs before the other two even though I am using Promises. What am I doing wrong?

When I console.log this.state.events and this.state.meets the terminal logs that these two states are empty arrays. This suggests that this function executes before function 2 and 3 finish and that I am doing my promises wrong. I can't seem to figure out my issue though and I am very confused. I know that the setState is working as well since once the page renders I have a button whose function console.logs those states and it executes properly.
I have tried formatting my promises in various ways but I have no idea how they resolve before my functions finish.
obtainEventsAndMeetsRefs = () => {
return Promise.resolve(
this.userRef.get().then(
doc => {
this.setState(
{
eventRefs: doc.data().Events,
meetingsRef: doc.data().Meets
}
)
}
)
)
}
obtainEvents() {
var that = this
return new Promise(function (resolve, reject) {
for (i = 0; i < that.state.eventRefs.length; i++) {
docRef = that.state.eventRefs[i]._documentPath._parts[1];
firebase.firestore().collection('All Events').doc(docRef).get().then(
doc => {
that.setState({
events: update(that.state.events, {
$push: [
{
eventName: doc.data().eventName,
eventDescription: doc.data().eventDescription,
startDate: doc.data().startDate,
endDate: doc.data().endDate,
location: doc.data().location,
privacy: doc.data().privacy,
status: doc.data().status,
attending: doc.data().attending
}
]
})
})
}
)
if (i == that.state.eventRefs.length - 1) {
console.log('1')
resolve(true)
}
}
})
}
obtainMeetings() {
var that = this
return new Promise(function (resolve, reject) {
for (i = 0; i < that.state.meetingsRef.length; i++) {
userRef = that.state.meetingsRef[i]._documentPath._parts[1];
meetRef = that.state.meetingsRef[i]._documentPath._parts[3];
ref = firebase.firestore().collection('Users').doc(userRef)
.collection('Meets').doc(meetRef)
ref.get().then(
doc => {
that.setState({
meets: update(that.state.meets, {
$push: [
{
headline: doc.data().headline,
agenda: doc.data().agenda,
startTime: doc.data().startTime,
endTime: doc.data().endTime,
date: doc.data().date,
name: doc.data().name
}
]
})
})
}
)
if (i == that.state.meetingsRef.length - 1) {
console.log('2')
resolve(true)
}
}
})
}
orderByDate = () => {
console.log(this.state.events)
console.log(this.state.meets)
}
componentDidMount() {
this.obtainEventsAndMeetsRefs().then(
() => this.obtainEvents().then(
() => this.obtainMeetings().then(
() => this.orderByDate()
)
)
)
}
I expected the output to be an array filled with data that I inputted using setState, not an empty array.
The crux of the issue is that obtainMeetings() and obtainEvents() don't return promises that resolve when their asynchronous operations are done. Thus, you can't know when their work is done and thus you can't sequence them properly.
There are all sorts of things wrong in this code. To start with, you don't need to wrap existing promises in another promise (the promise construction anti-pattern) because that leads to errors (which you are making). Instead, you can just return the existing promise.
For example, change this:
obtainEventsAndMeetsRefs = () => {
return Promise.resolve(
this.userRef.get().then(
doc => {
this.setState(
{
eventRefs: doc.data().Events,
meetingsRef: doc.data().Meets
}
)
}
)
)
}
to this:
obtainEventsAndMeetsRefs = () => {
return this.userRef.get().then(doc => {
this.setState({
eventRefs: doc.data().Events,
meetingsRef: doc.data().Meets
});
});
}
Then, chain rather than nest and return a promise that is linked to the completion/error of the operations.
So change this:
componentDidMount() {
this.obtainEventsAndMeetsRefs().then(
() => this.obtainEvents().then(
() => this.obtainMeetings().then(
() => this.orderByDate()
)
)
)
}
to this:
componentDidMount() {
return this.obtainEventsAndMeetsRefs()
.then(this.obtainEvents.bind(this))
.then(this.obtainMeetings.bind(this))
.then(this.orderByDate.bind(this))
}
or if you prefer:
componentDidMount() {
return this.obtainEventsAndMeetsRefs()
.then(() => this.obtainEvents())
.then(() => this.obtainMeetings())
.then(() => this.orderByDate())
}
These chain instead of nest and they return a promise that tracks the whole chain so that caller knows when the chain completed and/or if there was an error.
Then, if you want your for loop to execute serially, you can make it async and use await on the promise inside the loop like this:
async obtainMeetings() {
for (let i = 0; i < this.state.meetingsRef.length; i++) {
let userRef = this.state.meetingsRef[i]._documentPath._parts[1];
let meetRef = this.state.meetingsRef[i]._documentPath._parts[3];
let ref = firebase.firestore().collection('Users').doc(userRef)
.collection('Meets').doc(meetRef)
// make the for loop wait for this operation to complete
await ref.get().then(doc => {
this.setState({
meets: update(this.state.meets, {
$push: [{
headline: doc.data().headline,
agenda: doc.data().agenda,
startTime: doc.data().startTime,
endTime: doc.data().endTime,
date: doc.data().date,
name: doc.data().name
}]
});
});
});
// this return here isn't making a whole lot of sense
// as your for loop already stops things when this index reaches
// the end value
if (i == this.state.meetingsRef.length - 1) {
console.log('2')
return true;
}
}
// it seems like you probably want to have a return value here
return true;
}
This also looks like there's a problem with the missing declarations of i, userRef, meetRef and ref in your implementation. Those should be locally declared variables.
obtainEvents() needs the same redesign.
If you can't use async/await, then you can transpile or you'd have to design the loop to work differently (more of a manual asynchronous iteration).
If you can run all the async operations in the loop in parallel and just want to know when they are all done, you can do something like this:
obtainMeetings() {
return Promise.all(this.state.meetingsRef.map(item => {
let userRef = item._documentPath._parts[1];
let meetRef = item._documentPath._parts[3];
let ref = firebase.firestore().collection('Users').doc(userRef)
.collection('Meets').doc(meetRef)
// make the for loop wait for this operation to complete
return ref.get().then(doc => {
this.setState({
meets: update(this.state.meets, {
$push: [{
headline: doc.data().headline,
agenda: doc.data().agenda,
startTime: doc.data().startTime,
endTime: doc.data().endTime,
date: doc.data().date,
name: doc.data().name
}]
});
});
});
})).then(allResults => {
// process all results, decide what the resolved value of the promise should be here
return true;
});
}

How to wait firebase, fetch data and return array in method?

So i have this method in container component:
getProfilesOptions = () => {
const result = firebase.firestore().collection('roles').get().then(snapshot => {
const options = []
snapshot.docs.forEach(doc => {
options.push({ value: doc.id, label: doc.data().profile })
//console.log(doc.id) - everything ok, i'm fetching data correctyly
});
return options
})
console.log(result)//here i have pending result in debugger
return result
}
then, i'm passing link into child...child...child component.
Then i some child i want to call it, and get array as a result, and then set the state:
componentDidUpdate() {
if(this.state.isFocused && !this.state.options){
const options = this.props.getOptions()
this.setState({
options: options
})
}
}
Can i have a solution of this problem? Ofcourse i can pass props as result instead of props ref to the method, but can i use the method? How to improve getProfilesOptions?
You should wrap your firebase call in a Promise, because this is an async call.
getProfilesOptions = () => {
return new Promise(resolve => {
firebase.firestore().collection('roles').get().then(snapshot => {
const options = []
snapshot.docs.forEach(doc => {
options.push({ value: doc.id, label: doc.data().profile })
//console.log(doc.id) - everything ok, i'm fetching data correctyly
});
resolve(options)
})
}
}
And get the result in your component with .then()
componentDidUpdate() {
if(this.state.isFocused && !this.state.options){
this.props.getOptions().then(options => {
this.setState({
options: options
})
})
}
}
You can read more about Javascript Promises here

Testing Redux Thunk Action Creator

I've got a redux action creator that utilizes redux-thunk to do some logic to determine what to dispatch to the store. Its not promise-based, like an HTTP request would be, so I am having some issues with how to test it properly. Ill need a test for when the value meets the condition and for when it doesn't. Since the action creator does not return a promise, I cannot run a .then() in my test. What is the best way to test something like this?
Likewise, I believe it would be pretty straightforward testing the getRemoveFileMetrics() action creator as it actually does return a promise. But how can I assert that that will called if the value is removeFiles and meets the condition? How can that be written in the test?
Thanks in advance as this has had me stuck for the last couple of days.
Action Creators
export const handleSelection = (value, cacheKey) => {
return dispatch => {
if (value === "removeFiles") {
dispatch(getRemoveFileMetrics(cacheKey));
}
dispatch({ type: HANDLE_SELECTION, value });
};
};
export const getRemoveFileMetrics = cacheKey => {
return dispatch => {
dispatch({ type: IS_FETCHING_DELETE_METRICS });
return axios
.get(`../GetRemoveFileMetrics`, { params: { cacheKey } })
.then(response => {
dispatch({ type: GET_REMOVE_FILE_METRICS, payload: response.data });
})
.catch(err => console.log(err));
};
};
Jest
it("should dispatch HANDLE_SELECTION when selecting operation", () => {
const store = mockStore({});
const value = "switchVersion";
const expectedAction = [{
type: MOA.HANDLE_SELECTION,
value,
}]; // TypeError: Cannot read property 'then' of undefined
return store.dispatch(MOA.handleSelection(value)).then(() => {
const returnedActions = store.getActions();
expect(returnedActions).toEqual(expectedAction);
});
});
NEW EDIT
So based off of Danny Delott's answer to return a promise, I acheived a passing test as follows:
export const handleSelection = (value, cacheKey) => {
return dispatch => {
if (value === "removeFiles") {
return dispatch(getRemoveFileMetrics(cacheKey));
}
return new Promise((resolve, reject) => {
resolve(dispatch({ type: HANDLE_SELECTION, value }));
});
};
};
Is there a reason to explicitly NOT return a promise in your action creator? It looks like getRemoveFileMetrics is returning the promise, it just gets swallowed in handleSelection...
Easiest solution is to just return the promise:
export const handleSelection = (value, cacheKey) => {
return dispatch => {
if (value === "removeFiles") {
return dispatch(getRemoveFileMetrics(cacheKey));
}
dispatch({ type: HANDLE_SELECTION, value });
return new Promise();
};
};
Otherwise, you'll need make your assertions after the event loop is finished. You can do with a setTimeout wrapped in a Promise to get the .then behavior.
it("should dispatch HANDLE_SELECTION when selecting operation", () => {
const store = mockStore({});
const value = "switchVersion";
const expectedAction = [{
type: MOA.HANDLE_SELECTION,
value,
}];
store.dispatch(MOA.handleSelection(value));
// flush outstanding async tasks
return new Promise(resolve => {
setTimeout(resolve, 0);
})
.then(() => {
const returnedActions = store.getActions();
expect(returnedActions).toEqual(expectedAction);
});
});

Categories

Resources