I came across this scenario today, and would like to know the best approach to handling it.
The Vuex action uses Axios to post a record to the back end, catching any exceptions. Once the POST request returns, a mutator is called with the returned data. The issue is if an exception occurs in the mutator, it is not caught by error handling in the action, or the calling component.
Example
If the spread operator within the mutation is removed, an "object expected, got array" exception would be thrown, which I would expect to be caught within the .catch() function of the action call within the component, but it is not. Why and how can this be handled? If the solution to wrap the action call in an additional try/catch?
Action
async saveRecords({ commit }, records) {
return new Promise((resolve, reject) => {
axios.post("/records", records)
.then((res) => {
commit('addRecords', res.data.records)
resolve();
})
.catch((error) => {
reject(error);
})
})
}
Mutation
addCase: function (state, caseRecord) {
console.log("inside mutation")
state.cases.push(...caseRecord)
},
Component
this.saveRecords(records)
.catch((e) => {
// IExpecting exception from mutation to be caught here
})
});
Related
I'm having some issues with the way im handling errors in Vue. Currently im using a try/catch block in VueX to make some async calls. The catch block handles adding the error to a global toast. this is working very well.
async add({ dispatch }, form) {
try {
await firebase.add(form)
dispatch('getUpdatedList')
} catch (error) {
// Use the global error handle to handle the error
dispatch('error', error)
}
},
error ({ dispatch, commit }, errorMessage) {
console.log(errorMessage)
commit('ERROR', errorMessage)
}
// Add the Errors here
ERROR (state, val) {
state.errors = [val, ...state.errors]
},
The issue I have is this: When that error is caught in the block, it doesn't allow the errors to propagate to the components, so I can't handle the error in the component the way I would like. For example, if the client submits a form, but somehow that fails, the then block of a promise will still execute. So i can't reset the form, or stop a redirect, or do some UI tidy up on the client.
this.$store
.dispatch('add', this.data)
.then(() => {
//This gets hit no matter what the result
this.showSuccess = true
})
.catch(() => {
// More UI stuff
//Can only hit this when rethrowing the error
this.showLoading = false
this.showRegisterButton = true
})
I know that I can get around this by rethrowing the error, but that doesn't seem like the best way forward, as I've always believed that rethrowing an error is bad practice (although here it seems like a decent solution) Is there something simple that im missing?
This should be fine, with just one amendment.
You can re-throw the error to allow parent to catch using throw error
async add({ dispatch }, form) {
try {
await firebase.add(form)
dispatch('getUpdatedList')
} catch (error) {
// Use the global error handle to handle the error
dispatch('error', error)
// throw the handled error for another handler to catch
throw error
}
}
I recently started migrating things from jQ to a more structured framework being VueJS, and I love it!
Conceptually, Vuex has been a bit of a paradigm shift for me, but I'm confident I know what its all about now, and totally get it! But there exist a few little grey areas, mostly from an implementation standpoint.
This one I feel is good by design, but don't know if it contradicts the Vuex cycle of uni-directional data flow.
Basically, is it considered good practice to return a promise(-like) object from an action? I treat these as async wrappers, with states of failure and the like, so seems like a good fit to return a promise. Contrarily mutators just change things, and are the pure structures within a store/module.
actions in Vuex are asynchronous. The only way to let the calling function (initiator of action) to know that an action is complete - is by returning a Promise and resolving it later.
Here is an example: myAction returns a Promise, makes a http call and resolves or rejects the Promise later - all asynchronously
actions: {
myAction(context, data) {
return new Promise((resolve, reject) => {
// Do something here... lets say, a http call using vue-resource
this.$http("/api/something").then(response => {
// http success, call the mutator and change something in state
resolve(response); // Let the calling function know that http is done. You may send some data back
}, error => {
// http failed, let the calling function know that action did not work out
reject(error);
})
})
}
}
Now, when your Vue component initiates myAction, it will get this Promise object and can know whether it succeeded or not. Here is some sample code for the Vue component:
export default {
mounted: function() {
// This component just got created. Lets fetch some data here using an action
this.$store.dispatch("myAction").then(response => {
console.log("Got some data, now lets show something in this component")
}, error => {
console.error("Got nothing from server. Prompt user to check internet connection and try again")
})
}
}
As you can see above, it is highly beneficial for actions to return a Promise. Otherwise there is no way for the action initiator to know what is happening and when things are stable enough to show something on the user interface.
And a last note regarding mutators - as you rightly pointed out, they are synchronous. They change stuff in the state, and are usually called from actions. There is no need to mix Promises with mutators, as the actions handle that part.
Edit: My views on the Vuex cycle of uni-directional data flow:
If you access data like this.$store.state["your data key"] in your components, then the data flow is uni-directional.
The promise from action is only to let the component know that action is complete.
The component may either take data from promise resolve function in the above example (not uni-directional, therefore not recommended), or directly from $store.state["your data key"] which is unidirectional and follows the vuex data lifecycle.
The above paragraph assumes your mutator uses Vue.set(state, "your data key", http_data), once the http call is completed in your action.
Just for an information on a closed topic:
you don’t have to create a promise, axios returns one itself:
Ref: https://forum.vuejs.org/t/how-to-resolve-a-promise-object-in-a-vuex-action-and-redirect-to-another-route/18254/4
Example:
export const loginForm = ({ commit }, data) => {
return axios
.post('http://localhost:8000/api/login', data)
.then((response) => {
commit('logUserIn', response.data);
})
.catch((error) => {
commit('unAuthorisedUser', { error:error.response.data });
})
}
Another example:
addEmployee({ commit, state }) {
return insertEmployee(state.employee)
.then(result => {
commit('setEmployee', result.data);
return result.data; // resolve
})
.catch(err => {
throw err.response.data; // reject
})
}
Another example with async-await
async getUser({ commit }) {
try {
const currentUser = await axios.get('/user/current')
commit('setUser', currentUser)
return currentUser
} catch (err) {
commit('setUser', null)
throw 'Unable to fetch current user'
}
},
Actions
ADD_PRODUCT : (context,product) => {
return Axios.post(uri, product).then((response) => {
if (response.status === 'success') {
context.commit('SET_PRODUCT',response.data.data)
}
return response.data
});
});
Component
this.$store.dispatch('ADD_PRODUCT',data).then((res) => {
if (res.status === 'success') {
// write your success actions here....
} else {
// write your error actions here...
}
})
TL:DR; return promises from you actions only when necessary, but DRY chaining the same actions.
For a long time I also though that returning actions contradicts the Vuex cycle of uni-directional data flow.
But, there are EDGE CASES where returning a promise from your actions might be "necessary".
Imagine a situation where an action can be triggered from 2 different components, and each handles the failure case differently.
In that case, one would need to pass the caller component as a parameter to set different flags in the store.
Dumb example
Page where the user can edit the username in navbar and in /profile page (which contains the navbar). Both trigger an action "change username", which is asynchronous.
If the promise fails, the page should only display an error in the component the user was trying to change the username from.
Of course it is a dumb example, but I don't see a way to solve this issue without duplicating code and making the same call in 2 different actions.
actions.js
const axios = require('axios');
const types = require('./types');
export const actions = {
GET_CONTENT({commit}){
axios.get(`${URL}`)
.then(doc =>{
const content = doc.data;
commit(types.SET_CONTENT , content);
setTimeout(() =>{
commit(types.IS_LOADING , false);
} , 1000);
}).catch(err =>{
console.log(err);
});
},
}
home.vue
<script>
import {value , onCreated} from "vue-function-api";
import {useState, useStore} from "#u3u/vue-hooks";
export default {
name: 'home',
setup(){
const store = useStore();
const state = {
...useState(["content" , "isLoading"])
};
onCreated(() =>{
store.value.dispatch("GET_CONTENT" );
});
return{
...state,
}
}
};
</script>
I'm trying to improve the use of promises in some code that retrieves REST resources. I have a number of REST calls that make the perform the same sequence of actions:
Fetch the config resource from the server if it hasn't been obtained previously
Dispatch a flux action indicating the start of the request
Send a request for the actual resource
Parse the JSON in the response
Dispatch a flux action indicating success with the parsed data.
The code I currently use to do this is below.
getThingsFromServer() {
return getConfigIfNeeded().then(() => {
dispatchStartOfRequestAction();
return window.fetch(`${store.baseURL}/resource`)
.then((response) => {
if(response.ok) {
return response.json();
} else {
return Promise.reject(new Error(`${response.status} ${response.statusText}`));
}
}, (error) => {
return Promise.reject(new Error(`Network error: ${error.message}`));
})
.then((data) => {
dispatchSuccessAction(data);
}, (error) => {
return Promise.reject(new Error(`JSON parse error: ${error.message}`));
})
.catch((error) => {
dispatchFailureAction(error)
});
});
}
There are a number of error conditions I would like to be able to handle individually, after which I want to dispatch a failure action (which is done in the catch()).
At the moment, if one of the individual then() error handlers gets called, every subsequent then() error handler is also called before finally calling the catch(). I want just one individual handler and the catch to be called.
I could dispense with handling each error individually and use a single catch at the end of it all but various sources both support and vilify the practice of handling all these different errors in the same way at the end of a promise chain. Is there a "right" answer to this beyond personal opinion?
if one of the individual then() error handlers gets called, every subsequent then() error handler is also called
Yes, if you throw (or return a rejected promise) from then handler, the promise will get rejected and subsequent error handlers will get called. There's really no way around this. To differentiate the errors, you'll have to nest (see also here).
In your case, you'll want to use
dispatchStartOfRequestAction();
return fetch(`${store.baseURL}/resource`)
.then(response => {
if (response.ok) {
return response.json()
.catch(error => {
throw new Error(`JSON parse error: ${error.message}`);
});
} else {
throw new Error(`${response.status} ${response.statusText}`);
}
}, error => {
throw new Error(`Network error: ${error.message}`);
})
.then(dispatchSuccessAction, dispatchFailureAction);
We're having a hard time figuring out how context (or specifically { commit } is handled in chained promises with transpiled ES6 code. Below is one example of a Login action that authenticates and then subscribes using RxJS to the user as a stream. We need to commit several mutations throughout the process, but keep getting commit is not a function errors.
Does anyone know of or have an example of something like this or can anyone provide any basic guidelines on where and how context/commit are handled in this scenario - e.g. when can ES6 be used vs not, and/or where is context hoisted or not (if at all), and or is there a simpler approach to all this like maybe wrapping things all in a master promise? Since we need to potentially commit at each step in the promise chain, we cannot see how some of this could work:
const actions = {
login ({commit}, creds) { // need to commit here
commit('toggleLoading')
api.authenticate({
strategy: 'local',
...creds
})
.then(function (result) {
return api.passport.verifyJWT(result.accessToken)
})
.then(function ({commit}, payload) { //need to commit here
console.log(commit)
return api.service('users').get(payload.userId)
.subscribe(commit('setUser', user)) // need to commit here - but commit is not a function error
})
.catch(function ({commit}, error) {
commit('setErr', `ERROR AUTHENTICATING: {$err.message}`) // need to commit here but commit is not a function error
commit('toggleLoading')
})
}
All of the examples we find are way simplistic and only show one commit per action (or maybe 2 wrapped in an if). Any help or feedback is appreciated!
Firstly, the callback functions in .then and .catch take a single argument, you've coded two ... however, commit from the login arguments is still in scope, so it's quite simple to fix
Your code can be simplified as follows
const actions = {
login ({commit}, creds) {
commit('toggleLoading');
api.authenticate({strategy: 'local', ...creds})
.then(result => api.passport.verifyJWT(result.accessToken))
.then(payload => api.service('users').get(payload.userId).subscribe(commit('setUser', user)))
.catch(function (error) {
commit('setErr', `ERROR AUTHENTICATING: ${error.message}`);
commit('toggleLoading');
});
}
Note: you have {$err.message} in the .catch, whereas I beleive that should be ${error.message}
I'm learning React, along with pretty much all the necessary technology around it all at once - so I often get tripped up by things I should probably know already.
I've encountered a problem when it comes to error handling my async events. I've scoured the web and nothing really answers exactly what I'm looking for.
I'm currently using redux with redux-promise-middleware to handle the async actions, like this:
export function myFunc() {
return {
type: FETCH_FUNC,
payload: new Promise((resolve, reject) => {
fetch ('some/url/location/from/which/to/fetch')
.then( response => {
if (!response.ok){
throw new Error(response);
}
resolve(response.json());
}).catch(error => {
reject(error);
}),
})
};
}
There are two things here: first, the code works just fine when no errors are present. However, when I purposely create an error in the code the correct methods are firing but I still end up with the following error in my console:
Uncaught (in promise) Error: [object Response]
Should the .catch(...) block not be handling this? What am I missing? Should I be getting this anyway? If so, why?
Secondly, I've read that wrapping the fetch inside a new Promise is an anti-pattern, and there was an almost-hint that this may be what's causing problems here. All the examples I've come across use it in this fashion. What's the alternative? How do I fire the resolve/reject to dispatch the next actions without the wrapper?
Any help will be greatly appreciated. Thanks masters of the web.
-------------EDIT 1----------------
From the official redux-promise-middleware github examples, they have the following code:
export default function request(url, options) {
return new Promise((resolve, reject) => {
if (!url) reject(new Error('URL parameter required'));
if (!options) reject(new Error('Options parameter required'));
fetch(url, options)
.then(response => response.json())
.then(response => {
if (response.errors) reject(response.errors);
else resolve(response);
})
.catch(reject);
});
}
It seems to intention with the middleware is to wrap fetch inside a new Promise and catching any rejects. If anyone has a working alternative way of implementing this using redux-promise-middleware, or can elaborate on why its following this pattern that would be greatly appreciated.
-------------EDIT 2----------------
Not sure what the intended way of implementing this is or how to avoid the Uncaught error in the promise. Simply calling Promise.reject(...) results in an uncaught error unless you include error handling functions: Promise.reject(...).then(() =>{...}, error => {...}). Including this with the middleware results in the rejected action never being dispatched. I've moved away from redux-promise-middleware till I can find a suitable fix and/or implementation.
I guess what you are getting is the expected result and this is mentioned clearly in the middleware documentation:
The middleware dispatches rejected actions but does not catch rejected
promises. As a result, you may get an "uncaught" warning in the
console. This is expected behavior for an uncaught rejected promise.
It is your responsibility to catch the errors and not the
responsibility of redux-promise-middleware.
But if you ask about best practices this is what i ended up doing from long time ago and it's working perfectly with me:
1- For some promises you can do as mentioned in the documentation:
dispatch({
type: 'FOO_ACTION',
payload: new Promise(() => {
throw new Error('foo');
})
}).catch(error => {
// catch and handle error or do nothing
});
2- To catch all rejected promises globally add this middleware before the redux-promise-middleware as follow:
/**
* a utility to check if a value is a Promise or not
* #param value
*/
const isPromise = value => value !== null && typeof value === 'object' && typeof value.then === 'function';
export default () => {
const middleWares = [];
// global error middleware
middleWares.push(() => next => action => {
// If not a promise, continue on
if (!isPromise(action.payload)) {
return next(action);
}
/**
* include a property in `meta and evaluate that property to check if this error will be handled locally
*
* if (!action.meta.localError) {
* // handle error
* }
*
* The error middleware serves to dispatch the initial pending promise to
* the promise middleware, but adds a `catch`.
*/
if (!action.meta || !action.meta.localError) {
// Dispatch initial pending promise, but catch any errors
return next(action).catch(error => {
if (config.showErrors) { // here you can decide to show or hide errors
console.log(`${action.type} unhandled rejection caught at middleware with reason: ${JSON.stringify(error.message)}.`);
}
return error;
});
}
return next(action);
});
// middleware
middleWares.push(thunk);
middleWares.push(promise());
middleWares.push(logger());
return applyMiddleware(...middleWares);
}
i guess this is exactly what you are looking for ;)
Extra I highly recommend axios over fetch for the following reasons:
the axios module automatically reject the promise if the request has an error code which is something you need to keep manually handle in fetch
in axios you can create instance with default base-url,header,interceptors ...
in axios you can cancel any previous request using a token this is extremely useful specially for autocomplete and chat applications
also axios internally automatically switch between xhr and http modules to perform the ajax request based on the environment (NodeJs or Browser), i personally used the same redux actions in electron, nodejs, browser and react-native and it's all working fine
Following up on caisah 's comment, get rid of the indirection. You can resolve or reject a promise by simply resolving or rejecting with a new promise object
export function myFunc() {
return {
type: FETCH_FUNC,
payload: fetch ('some/url/location/from/which/to/fetch')
.then(response => {
if (!response.ok){
throw new Error(response);
}
return Promise.resolve(response.json());
}).catch(error => {
return Promise.reject(error)
}),
})
};
}
myFunc().payload.then(json => /* do stuff with json*/)
P.S the returns may be redundant.
I’ve used "Catching Errors Globally" presented in "Catching Errors Thrown by Rejected Promises", as shown, when calling applyMiddleware the errorMiddleware should be before the promiseMiddleware. To filter the action types where to apply this middleware i've preferred a regex:
This is the store creation:
import { createStore, combineReducers, applyMiddleware } from 'redux';
import promiseMiddleware from 'redux-promise-middleware';
import errorMiddleware from './errorMiddleware';
import adultosReducer from './adultosReducer';
const rootReducer = combineReducers({
adultosReducer
});
const composeStoreWithMiddleware = applyMiddleware(errorMiddleware, promiseMiddleware())(
createStore
);
export default composeStoreWithMiddleware(rootReducer);
This is the error middleware:
import isPromise from 'is-promise';
import _ from 'lodash';
const tiposAction = /^ADULTO/i;
export default function errorMiddleware() {
return next => action => {
// If not a promise, continue on
if (!isPromise(action.payload)) {
return next(action);
}
console.log('errorMiddleware: action.type', action.type);
if (action.type.match(tiposAction)) {
// Dispatch initial pending promise, but catch any errors
return next(action).catch(error => {
console.log('catching action', error);
return error;
});
}
return next(action);
};
}
That way you show gently to the user the error because the rejected action is dispatched without the Unhandled promise. And of course there is no need to add redux-thunk.