I have a redux action function that returns a promise. Inside that function body, there is another async function that returns a callback when it finished the call. Then I call this function in another place chaining it with .then()
but when debugging it in the browser with breakpoints, the promise function exists after the first two lines of code
return new Promise((resolve, reject) => { return (dispatch, getState) => {
is it because of the second return statement? This is the react/redux
code so I must have a second return statement for my async Thunk redux action. How can I achieve this? So that I could call it in another redux action and chain it with handleProfileImageUploadToS3().then(() => {...})
?
Full function body:
export const handleProfileImageUploadToS3 = () => { return new Promise((resolve, reject) => { return (dispatch, getState) => { const settingsState = getState().BusinessSettings const userState = getState().User const imageUpload = true if (!settingsState.get('logoImage') || settingsState.get('logoImage') === null) { reject('no image selected') return } Utilities.uploadFileToS3( imageUpload, settingsState.get('logoImage'), 'apiurl', `profiles/${userState.get('id')}`, (error, url) => { if (error) { dispatch(uploadProfileSettingsImageError(error)) return } dispatch(updateProfileImageUrlAfterUpload(url)) resolve(url) } ) } }) }
Advertisement
Answer
I’m not seeing your whole codebase but there are some red flags in this function that I’m concerned about.
- The
BusinessSettings
andUser
properties of your state seem to be non-serializable objects with aget()
method. - If the S3 upload gives an error then you
dispatch
andreturn
but you neverresolve
orreject
thePromise
. - The rejection from
reject('no image selected')
is unlikely to be caught anywhere. - It seems like an un-uploaded image should be something that is stored in your UI state and passed as an argument rather than stored in Redux. You would want to store the URL after it’s uploaded. This also removes the need for that rejection.
You are addressing two separate concerns in this function and I would recommend that you separate the two.
First is that you have a function Utilities.uploadFileToS3
which uses a success/failure callback and you want to convert it to an async function (one that returns a Promise
).
I would make a helper that takes just the arguments which vary and ignores those that are constant (like 'apiurl'
).
const asyncUploadFileToS3 = (image, userId) => { return new Promise((resolve, reject) => { Utilities.uploadFileToS3( true, image, "apiurl", `profiles/${userId}`, (error, url) => (url ? resolve(url) : reject(error)) ); }); };
Now that you have that part settled, you can approach writing the thunk in a more typical way. You can return a Promise
by .then()
chaining or by making the function async
and using a try
/catch
block. By we don’t need to wrap the whole thing in a new Promise
because we’ve dealt with that on the asyncUploadFileToS3
function.
You can return a result from the thunk and chain it, but I’m not sure what actually makes the most sense here.
export const handleProfileImageUploadToS3 = (image) => async ( dispatch, getState ) => { const userId = getState().user.id; try { const url = await asyncUploadFileToS3(image, userId); dispatch(updateProfileImageUrlAfterUpload(url)); return "this is the result"; } catch (error) { dispatch(uploadProfileSettingsImageError(error)); return "there was an error"; } };
export default function App() { const dispatch = useDispatch(); const avatar = useSelector((state) => state.user.imageUrl); const onClick = () => { const image = new Blob(); dispatch(handleProfileImageUploadToS3(image)).then( // logs "this is the result" or "there was an error" (returned) => console.log("finished", returned) ); }; return ( <div> <button onClick={onClick}>Upload</button> {avatar ? <div>Avatar URL: {avatar}</div> : <div>No Avatar</div>} </div> ); }