I'm new to Nuxt and I'm facing an issue that I don't understand.
If i code something like:
const resp1 = await this.$axios.$post('urlCall1', {...dataCall1});
this.$axios.$post('urlCall2', {...dataCall2, resp1.id});
The resp1.id is properly set in the 2nd axios call => we wait for the first call to be completed before doing the 2nd one.
However, when I define asyn actions in my vuex store ex:
async action1({ commit, dispatch }, data) {
try {
const respData1 = await this.$axios.$post('urlCall1', { ...data });
commit('MY_MUTATION1', respData1);
return respData1;
} catch (e) {
dispatch('reset');
}
},
async action2({ commit, dispatch }, data, id) {
try {
const respData2 = await this.$axios.$post('urlCall2', { ...data });
commit('MY_MUTATION2', respData2);
} catch (e) {
dispatch('reset');
}
}
and then in my vue component I fire those actions like:
const resp1 = await this.$store.dispatch('store1/action1', data1);
this.$store.dispatch('store2/action2', data2, resp1.id);
resp1.id is undefined in action2.
I also tried managing promise the "old way":
this.$store.dispatch('store1/action1', data1).then(resp1 => this.$store.dispatch('store2/action2', data2, resp1.id))
The result is still the same => id = undefined in action2
Can you guys please tell me where I'm wrong ?
Thanks in advance.
Last note: the 2 actions are in different stores
Vuex doesn't allow multiple arguments, so you have to pass it through as an object, so it could look like:
this.$store.dispatch('store2/action2', { ...data2, id: resp1.id });
And then in the store:
async action2({ commit, dispatch }, { id, ...data }) {
try {
const respData2 = await this.$axios.$post('urlCall2', { ...data });
commit('MY_MUTATION2', respData2);
} catch (e) {
dispatch('reset');
}
}
Related
I'm experiencing a strange behaviour with created() and mounted() in Vue.js. I need to set 2 lists in created() - so it means those 2 lists will help me to create a third list which is a merge.
Here is the code :
// return data
created () {
this.retrieveSellOffers();
this.getAllProducts();
},
mounted () {
this.mergeSellOffersProducts();
},
methods: {
retrieveSellOffers() {
this.sellerId = localStorage.sellerId;
SellOfferServices.getAllBySellerId(this.sellerId)
.then((response) => {
this.sellOffers = response.data;
console.log("this.sellOffers");
console.log(this.sellOffers);
})
.catch((e) => {
console.log(e);
});
},
getAllProducts() {
ProductServices.getAll()
.then((response) => {
this.products = response.data;
console.log("this.products");
console.log(this.products);
})
.catch((e) => {
console.log(e);
});
},
mergeSellOffersProducts () {
console.log(this.products) // print empty array
console.log(this.sellOffers) // print empty array
for (var i = 0; i < this.sellOffers.length; i++) {
if (this.sellOffers[i].productId === this.products[i]._id) {
this.arr3.push({id: this.sellOffers[i]._id, price: this.sellOffers[i].price, description: this.products[i].description});
}
}
this.arr3 = this.sellOffers;
},
}
//end of code
So my problem is when I enter in mergeSellOffersProducts(), my 2 lists are empty arrays :/
EDIT :
This way worked for me :
async mounted() {
await this.retrieveSellOffers();
await this.getAllProducts();
this.mergeSellOffersProducts();
},
methods: {
async retrieveSellOffers() {
this.sellerId = localStorage.sellerId;
this.sellOffers = (await axios.get('link/api/selloffer/seller/', { params: { sellerId: this.sellerId } })).data;
},
async getAllProducts() {
this.products = (await axios.get('link/api/product')).data;
},
}
I think the reason is: Vue does not wait for the promises to resolve before continuing with the component lifecycle.
Your functions retrieveSellOffers() and getAllProducts() contain Promise so maybe you have to await them in the created() hook:
async created: {
await this.retrieveSellOffers();
await this.getAllProducts();
}
So I tried to async my 2 methods :
async retrieveSellOffers() {
this.sellerId = localStorage.sellerId;
this.sellOffers = (await axios.get('linkhidden/api/selloffer/', { params: { sellerId: '615b1575fde0190ad80c3410' } })).data;
console.log("this.sellOffers")
console.log(this.sellOffers)
},
async getAllProducts() {
this.products = (await axios.get('linkhidden/api/product')).data;
console.log("this.products")
console.log(this.products)
},
mergeSellOffersProducts () {
console.log("here")
console.log(this.sellOffers)
console.log(this.products)
this.arr3 = this.sellOffers;
},
My data are well retrieved, but yet when I enter in created, the two lists are empty...
You are calling a bunch of asynchronous methods and don't properly wait for them to finish, that's why your data is not set in mounted. Since Vue does not await its lifecycle hooks, you have to deal with the synchronization yourself.
One Vue-ish way to fix it be to replace your method mergeSellOffersProducts with a computed prop (eg mergedSellOffersProducts). Instead of generating arr3 it would simply return the merged array. It will be automatically updated when products or sellOffers is changed. You would simply use mergedSellOffersProducts in your template, instead of your current arr3.
If you only want to update the merged list when both API calls have completed, you can either manually sync them with Promise.all, or you could handle this case in the computed prop and return [] if either of the arrays is not set yet.
When you're trying to merge the 2 lists, they aren't filled up yet. You need to await the calls.
async created () {
await this.retrieveSellOffers();
await this.getAllProducts();
},
async mounted () {
await this.mergeSellOffersProducts();
},
I work with vue and apexchart. I call the api with the async method, to make sure that I am showing the correct information. I'm using console.Log and the array is correct. So does anyone know what is happening here? Thanks.
Probably your graph is created before the data of your api arrives. Try this :
data: () => ({
series: null
options: { ... }
}),
async mounted() {
this.loaded = false;
try {
const res = await fetch(urlYandex);
const json = await res.json()
this.series = json.data[0].metrics[0];
this.loaded = true;
} catch (e) {
console.error(e);
}
},
your apexchart you add like this :
<apexchart :options="options" :series="series" v-if="loaded"></apexchart>
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 am trying to debounce a method within a Vuex action that requires an external API.
// Vuex action:
async load ({ state, commit, dispatch }) {
const params = {
period: state.option.period,
from: state.option.from,
to: state.option.to
}
commit('SET_EVENTS_LOADING', true)
const res = loadDebounced.bind(this)
const data = await res(params)
console.log(data)
commit('SET_EVENTS', data.collection)
commit('SET_PAGINATION', data.pagination)
commit('SET_EVENTS_LOADING', false)
return data
}
// Debounced method
const loadDebounced = () => {
return debounce(async (params) => {
const { data } = await this.$axios.get('events', { params })
return data
}, 3000)
}
The output of the log is:
[Function] {
cancel: [Function]
}
It is not actually executing the debounced function, but returning to me another function.
I would like to present a custom debounce method which you can use in your vuex store as
let ongoingRequest = undefined;
const loadDebounced = () => {
clearTimeout(ongoingRequest);
ongoingRequest = setTimeout(_ => {
axios.get(<<your URL>>).then(({ data }) => data);
}, 3000);
}
This method first ensures to cancel any ongoing setTimeout in the pipeline and then executes it again.
This can be seen in action HERE
I created a recordSaga function, its target is to record what actions have been dispatched during the saga.
export const recordSaga = async (saga, initialAction, state) => {
const dispatched = [];
const done = await runSaga(
{
dispatch: action => dispatched.push(action),
getState: () => state,
},
saga,
initialAction,
).done;
return {
dispatched,
done,
};
};
so let's say my saga is this one
export function* mySaga() {
const needToSave = yield select(needToSaveDocument);
if (needToSave) {
yield put(saveDocument());
yield take(SAVE_DOCUMENT_SUCCESS);
}
yield put(doSomethingElse())
}
I want to write two tests, which I expect to be the following
describe('mySaga', async () => {
it('test 1: no need to save', async () => {
const state = { needToSave: false }
const { dispatched } = await recordSaga(mySaga, {}, state);
expect(dispatched).toEqual([
doSomethingElse()
])
})
it('test 2: need to save', async () => {
const state = { needToSave: true }
const { dispatched } = await recordSaga(mySaga, {}, state);
expect(dispatched).toEqual([
saveDocument(),
doSomethingElse()
])
})
})
However, for the test 2 where there is a take in between, and of course jest (or its girlfriend jasmine) is yelling at me: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
I know it is because runSaga is waiting for the take(SAVE_DOCUMENT_SUCCESS), but how can I mock that up ?
The answer stdChannel().put({type, payload})
Why ?
Using stdChannel you can dispatch after the first run.
How ?
import stdChannel;
add to the first param in runSaga;
call stdChannel().put(SAVE_DOCUMENT_SUCCESS);
Example
what worked for me
I left the first test as it is the expected final result, but the solution comes on the last 2.
import { runSaga, stdchannel } from 'redux-saga'
let dispatchedActions = [];
let channel;
let fakeStore;
beforeEach(() => {
channel = stdChannel(); // you have to declare the channel to have access to it later
fakeStore = {
channel, // add it to the store in runSaga
getState: () => "initial",
dispatch: (action) => dispatchedActions.push(action),
};
});
afterEach(() => {
global.fetch.mockClear();
});
it("executes getData correctly", async () => {
await runSaga(fakeStore, getData, getAsyncData("test")).toPromise();
expect(global.fetch.mock.calls.length).toEqual(1);
expect(dispatchedActions[0]).toEqual(setData(set_value));
});
it("triggers takeLatest and call getData(), but unfortunately doesn't resolve promise", async () => {
await runSaga(fakeStore, rootSaga)// .toPromise() cannot be used here, as will throw Timeout error
channel.put(getAsyncData("test")); // if remove this line, the next 2 expects() will fail
expect(global.fetch.mock.calls.length).toEqual(1);
// expect(dispatchedActions[1]).toEqual(setData(set_value)); // will fail here, but pass on the next it()
});
it("takes the promised data from test above", () => {
expect(dispatchedActions[1]).toEqual(setData(set_value));
});
this answer (about true code, not tests) helped me
By looking at recordSaga:
export const recordSaga = async (saga, initialAction, state) => {
It seems that you should pass {type: SAVE_DOCUMENT_SUCCESS} as a second argument (i.e initialAction). That should trigger the take effect.