Inside a vue action, I want to poll my backend (call another async action like described below? ) until the something has finished processing (frontend shows a loader until progress is over).
In other words, I want to repeatedly make an API call looking like :
actions: {
load: async ({dispatch, commit, state}, {args}) => {
await dispatch('progressPolling', {progressUrl, userId})
// continue ONLY when progress has finished, else blocks at this statement
...
}
progressPolling: async ({dispatch, commit}, {progressUrl, userId}) => {
var finished = false
while(!finished) : {
var progress = await dispatch('getResource', {url: progressUrl, userId: userId})
finished = (progress.data.finished === true)
// timeout of 2 secs
}
}
...
}
My understanding is that that you can use the setTimeout() statement to wait between calls, but am not sure how to turn this into a non-blocking while statement, or simply what is the best practice to achieve this.
EDIT: "Non-blocking" because I was originally using a wait() statement in a while loop. That made the UI freeze each time it had to wait.
I would use recursive function to do this.
progressPolling: async ({dispatch, commit}, {progressUrl, userId}) => {
// Define a recursive function
async function getResource (resolve) {
const progress = await dispatch('getResource', {url: progressUrl, userId: userId})
// Recurse when not finished
if (progress.data.finished !== true) {
setTimeout( () => getResource(resolve), 2000);
}
}
// Kick the recursive function
return new Promise (resolve => getResource(resolve))
// Finished!
}
Related
trying to refactor the saga code into rxjs epic. It's fetching sessions from the server
yield race([
call(fetchSessions, sessionsList), //complete retrieving sessions
call(sessionWatchdog, 20000), //or timeout with 20 seconds of inactivity
]);
function* fetchSessions(list: Array<number>): Generator<*, *, *> {
console.log('FETCH ALL', list.length);
for (let id of list) {
yield fork(fetchSession, id);
}
}
function* sessionWatchdog(duration: number): Generator<*, *, *> {
while (true) {
let { retrieved, timeout } = yield race({
retrieved: take('SESSION_RETRIEVED'),
timeout: delay(duration),
});
if (timeout) {
return 'TIMEOUT';
}
}
}
The fetch session is an async function that retrieves a single session. I'm not sure how to make sure to make an equivalent epic. After each session is fetched need to make sure it was retrieved or timeout and handle that.
That's what I have now, but don't understand how to make it do the same as saga code with sessionWatchdog
export const fetch_all_sessions = (
action$: Obs<*>,
state$: typeof StateObservable
): Obs<*> =>
action$.pipe(
filter(action => action.type === 'FETCH_ALL_SESSIONS'),
switchMap(action => {
let list = action.list;
from(list).pipe(
map(id => {
return fetchSession(id);
})
);
return of({ type: '' });
})
);
Thanks for your help or advice
I'm trying to check that a method was not called again after a certain action.
My test:
it('if query is less than 3 symbols, api call is not made', () => {
cy.spy(foo, 'bar').as('bar');
cy.get('input').type('12').then(() => {
cy.get('#bar').its('callCount').then(res => {
expect(res).to.eq(1); // a basic check after mounted hook
});
});
});
My component:
async mounted(): Promise<void> {
await this.foo.bar();
}
async getSearchResults(): Promise<void> {
if (this.searchQuery.length < 3) {
return;
}
await this.foo.bar();
}
The problem is that bar was already called on mount, and it could have been called multiple times before, if query length was valid. I was thinking about saving bar's callCount to a variable and checking it after call, but that looks ugly. Kinda stuck here, any ideas are welcome.
It's not an issue. The call count is started at the point you set up the spy, not when the component is mounted.
Try this:
const foo = {
bar: () => console.log('bar called')
}
it('starts with a clean callcount', () => {
foo.bar() // make a call
cy.spy(foo, 'bar').as('bar'); // callCount === 0 on setup
cy.get('#bar')
.its('callCount')
.should('eq', 0) // passes
});
Even if you have some callcount from another test, you can always reset it before the current test:
it('allows reset of spy callCount', () => {
cy.spy(foo, 'bar').as('bar'); // callCount === 0 on setup
foo.bar() // make a call, count is now 1
cy.get('#bar').invoke('resetHistory') // remove prior calls
cy.get('#bar')
.its('callCount')
.should('eq', 0) // passes
});
I believe you can get the initial call count, and then wrap your test in that.
it('if query is less than 3 symbols, api call is not made', () => {
cy.spy(foo, 'bar').as('bar');
cy.get('#bar').its('callCount').then((initRes) => {
cy.get('input').type('12').then(() => {
cy.get('#bar').its('callCount').then(res => {
expect(res).to.eq(initRes); // a basic check after mounted hook
});
});
});
});
You would probably want to do a test that this would fail, to make sure that Cypress is getting '#bar' again.
I have page Login.vue and I am using a strategy if the user already logged in then go to Home Component else stay same
My Code
mounted() {
this.checkAlreadyLoggedIn();
},
methods: {
async checkAlreadyLoggedIn() {
this.busy = true;
await this.$store.dispatch("attempt");
this.busy = false;
if (this.$store.getters.loggedIn) {
this.$navigateTo(Home, {
clearHistory: true
});
}
},
}
attempt action request to server and get users detail
but it seems it triggers this.$store.getters.loggedIn early
Thank you
In order to wait properly before checking the getter, and trigger the busy state, return the promise from the attempt action:
attempt({ state, commit }) {
return axios.post(...) // <-- Returning the promise manually
.then(response => {
// Commit change
})
},
Or with async / await:
async attempt({ state, commit }) { // <-- async keyword returns promise automatically
const response = await axios.post(...);
// Commit change
}
Here is a demo
I'm building a React Native app and when one button is pressed I want to call two functions. The first one will make a get call and set the state loading: true, the second one will show a popup with the result of that get call.
I am calling the second function only if loading === false but it is executed immediately after the first one before the state can change, because loading is false by default. I can resolve this with setTimeout but I was wondering if there was a cleaner way to do this.
onPress() {
this.props.getUsers();
setTimeout(() => {
if (this.props.loading === false) {
this.props.popUpVisible();
}
}, 1000);
}
You can create callback function for that
getUsers = (callback) => {
//do whatever you want
//when it's done
callback();
}
In onPress function
onPress = () => {
this.props.getUsers(() => {
if (this.props.loading === false) {
this.props.popUpVisible();
}
});
}
setState Function can take two param:
setState(updater, callback)
setState({loading:true},() => {
//this fires once state.loading === true
})
Use getDerivedStateFromProps. It always fire when component's props change.
Below is the example.
class EmailInput extends Component {
state = {
email: this.props.defaultEmail,
prevPropsUserID: this.props.userID
};
static getDerivedStateFromProps(props, state) {
// Any time the current user changes,
// Reset any parts of state that are tied to that user.
// In this simple example, that's just the email.
if (props.userID !== state.prevPropsUserID) {
return {
prevPropsUserID: props.userID,
email: props.defaultEmail
};
}
return null;
}
// ...
}
Hi I have this create and update zone function. After the API call success. I will callback again the dispatch on vuex store. Then Go back to main zone page.
The problem is it will took around 5 secs to get the list the results of dispatch. Making my list not updated.
How to know if the dispatch is done before going back to the page?
loadZones(){
this.$store.dispatch('getZones');
},
createOrUpdateZone(zone, region_checkbox, callback){
this.$http.post(process.env.API_URL +'/api/.....)
.then(res=> {
if(res.data.success == true){
this.loadZones();
this.$router.push('/zone');
} else{
this.has_error = true;
})
}
Vuex actions always return Promise, just add return when you create request in your getZones action to chain your ajax request promise with returned by action, then you can do something like this:
//... in actions, example
getZones(context) {
return some_http_request()
}
//...
loadZones(){
return this.$store.dispatch('getZones');
},
createOrUpdateZone(zone, region_checkbox, callback){
this.$http.post(process.env.API_URL +'/api/.....)
.then(res=> {
if(res.data.success == true){
// next "then" will be invoked when this request will be done
return this.loadZones();
}
else throw new Error();
})
.then(() => {
this.$router.push('/zone');
})
.catch(() => this.has_error = true);
}
You can use async await.
When you make loadZones async function, in it you can use await on the dispatch getZones. But remember that the getZones action should return a promise. I believe that it already returning a promise, so you just have to add async await.
async loadZones(){
await this.$store.dispatch('getZones');
},
createOrUpdateZone(zone, region_checkbox, callback){
this.$http.post(process.env.API_URL +'/api/.....)
.then(res=> {
if(res.data.success == true){
this.loadZones();
this.$router.push('/zone');
} else{
this.has_error = true;
})
}