I'm writing a redux application using thunk middleware and experienced an issue regarding promises inside the async actions.
db.getLoggedInUser() returns a promise (Bluebird) that is fulfilled with a user object or a null (corresponding to the current logged in user).
If the user is logged in, I would like for fetch some relevant data, where userActions.fetchUserInfo is another async action (like initializeApp).
In the beginning of the process I'm dispatching a 'start_loading' action and after the all process is done - I'm dispatching a 'stop_loading' action.
export const initializeApp = () => dispatch => {
dispatch(loadingActions.startLoading());
return db.getLoggedInUser()
.then((user) => {
if (user) {
return dispatch(userActions.fetchUserInfo(user.id));
}
})
.catch(dbError => dispatch(errorActions.reportError(dbError.message)))
.finally(() => dispatch(loadingActions.stopLoading()));
};
When dispatching initializeApp() I'm getting the following error:
db.getLoggedInUser(...).then(...).catch(...).finally is not a function
I'm getting this error regardless the resolved value of getLoggedInUser().
Anyone has an idea?
Related
Whenever if there is any asynchronous task performing related to component and that component unmounts then React generally gives this warning -
Can't perform a React state update on an unmounted component This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
I found some solutions over internet to use isMount flag (either by using it with useRef or useState) as true and then updating it to false on component unmount. But is that a proper solution as per React site using isMount is a antipattern.
https://reactjs.org/blog/2015/12/16/ismounted-antipattern.html
In future version of React, you probably won't need to fix this. As React dev team is going to remove this warning in future release. The main reason being this warning can be false positive sometimes.
As per this commit by Dan Abramov
https://github.com/facebook/react/pull/22114
But what are the solutions to fix this till that version release -
Using isMountState anti-pattern - If someone is checking isMounted in his code to fix this issue, then that person is already too late in performing this check, as this warning indicates the same check is done by React and it failed.
If this issue is because of an asynchronous call. Then one possible solution is to use AbortController API in your code.
AbortController API helps in aborting any ajax call that is already made. Cool stuff. Right?
More details on this can be found here
Abort Controller1
So if it is a fetch API one can use AbortController API like this
useEffect(() => {
const abortController = new AbortController()
// creating an AbortController
fetch(url, {
signal: abortController.signal
})
// passing the signal to the query
.then(data => {
setState(data)
// if everything went well, set the state
})
.catch(error => {
if (error.name === 'AbortError') return
// if the query has been aborted, do nothing
throw error
})
return () => {
abortController.abort()
// stop the query by aborting on the AbortController on unmount
}
}, [])
If you are using axios, then good news is axios also provides support for AbortController APIs -
const fetchData = async (params) => {
setLoading(true);
try {
const result = await axios.request(params);
// more code here
} catch (curError) {
if (axios.isCancel(curError)) {
return false;
}
// error handling code
}
return null;
};
useEffect(() => {
const cancelToken = axios.CancelToken;
const source = cancelToken.source();
fetchData({
...axiosParams,
cancelToken: source.token
});
return () => {
source.cancel("axios request cancelled");
};
}, []);
I have an app that fetches some user info on render. So when the app first boots up it fetches the data with the use of getUserInformation() function. User doesn't need to manually log in, the app is inside the company's internal network.
export function getUserInformation() {
return function (dispatch) {
getUser()
.then((data) => {
dispatch(
{type: GET_USER_SUCCESS, response: data}
)
})
.catch((error) => {
dispatch(
{type: GET_USER_FAILURE, response: error}
)
})
}
}
Now I want to fetch the version of the app to be available in the whole app. But the API call can only be fired once the user is logged in (so getUser() was called successfully). Should I just add the
.then(getVersion())
in the getUserInformation() action?
It doesn't seem clean but I have no idea how can I approach it differently.
Action creator is a proper place to dispatch actions in sequence. The documentation covers this:
Using an async middleware like Redux Thunk certainly enables scenarios such as dispatching multiple distinct but related actions in a row, dispatching actions to represent progression of an AJAX request, dispatching actions conditionally based on state, or even dispatching an action and checking the updated state immediately afterwards.
In case user information and version actions need to be tested separately (they should be located in different modules) or be used separately, action creators can be combined. This requires to return promises to chain them. This also shows the limitation of redux-thunk:
function getUserInformation() {
return async (dispatch) => {
try {
dispatch(
{type: GET_USER_SUCCESS, response: await getUser()}
)
} catch (error) {
dispatch(
{type: GET_USER_FAILURE, response: error}
)
}
};
}
...
function getVersion() {
return async (dispatch) => {...};
}
...
function getInitialData() {
return async (dispatch, getState) => {
await getUserInformation()(dispatch);
// we need to use getState to check if there was an error
// because getUserInformation returns a fulfilled promise any way
await getVersion()(dispatch);
};
}
It would make sense be to re-throw an error from getUserInformation, but it would be bad in case it's used separately from getInitialData because this would result in unhandled rejection. The alternative is even worse, to check if there was an error with getState().
This scenario requires a more sophisticated middleware than redux-thunk which is dead simple - possibly a custom middleware that is based on it and is capable of handling rejections.
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 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.
I am building a React-Redux application that I have a shows a cancel button while a certain HTTP request in in progress. If the request executes successfully, the UI shows the results and if the user presses the cancel button, the UI shows something different.
Right now I am using the following function to kick off my request and update my Redux store once the request is finished.
export function doFetch(url) {
return dispatch => {
fetch(url)
.then((json) => dispatch({type: types.MY_SUCCESS_ACTION}, json))
.catch((error) => dispatch({type: types.MY_FAILURE_ACTION, error}));
}
}
Now the above code doesn't handle the case in which the use clicks the cancel button. Now ES7 is doing some work with cancelable promises which could fit my use case because then I would have a reference to my request promise which I could then cancel if the user hits the cancel button.
How would I do this in the meantime though. I need some way so that if the user hits the cancel button, I can dispatch an action which will drop the current HTTP request.
Solution
import Bluebird from 'bluebird';
Bluebird.config({
cancellation: true
});
fetch.promise = Bluebird;
let curReqPromises = [];
function doFetch(url) {
return dispatch => {
curReqPromises.push(bluebirdRequest(url)
.then((json) => dispatch({type: types.MY_SUCCESS_ACTION}, json))
.catch((error) => dispatch({type: types.MY_FAILURE_ACTION, error}));
}
}
function bluebirdRequest(url) {
return new Bluebird((resolve, reject, onCancel) => {
makeFetch(url)
.then((json) => resolve(json))
.catch((error) => reject(json));
onCancel(() => {
console.log('I was cancelled');
});
});
}
There are a couple of ways to make promises cancellable.
Use rxjs
Use bluebird
If you're not interested in doing either one of them then you care for the cancellation manually by creating a cancellation action and then ignoring the result of that fetch when the state is cancelled. Once the promise resolves you can reset the state to notCancelled but avoid state mutation from the fetch. It's messy and that is one of the selling points for rxjs or bluebird, both of which have cancellation built in.