diff --git a/src/middleware.js b/src/middleware.js index 1cb08ac..872b6ed 100644 --- a/src/middleware.js +++ b/src/middleware.js @@ -10,160 +10,162 @@ import { normalizeTypeDescriptors, actionWith } from './util'; * @access public */ function apiMiddleware({ getState }) { - return next => async action => { + return next => action => { // Do not process actions without an [RSAA] property if (!isRSAA(action)) { return next(action); } - // Try to dispatch an error request FSA for invalid RSAAs - const validationErrors = validateRSAA(action); - if (validationErrors.length) { - const callAPI = action[RSAA]; - if (callAPI.types && Array.isArray(callAPI.types)) { - let requestType = callAPI.types[0]; - if (requestType && requestType.type) { - requestType = requestType.type; + return (async () => { + // Try to dispatch an error request FSA for invalid RSAAs + const validationErrors = validateRSAA(action); + if (validationErrors.length) { + const callAPI = action[RSAA]; + if (callAPI.types && Array.isArray(callAPI.types)) { + let requestType = callAPI.types[0]; + if (requestType && requestType.type) { + requestType = requestType.type; + } + next({ + type: requestType, + payload: new InvalidRSAA(validationErrors), + error: true + }); } - next({ - type: requestType, - payload: new InvalidRSAA(validationErrors), - error: true - }); - } - return; - } - - // Parse the validated RSAA action - const callAPI = action[RSAA]; - var { endpoint, headers, options = {} } = callAPI; - const { method, body, credentials, bailout, types } = callAPI; - const [requestType, successType, failureType] = normalizeTypeDescriptors( - types - ); - - // Should we bail out? - try { - if ( - (typeof bailout === 'boolean' && bailout) || - (typeof bailout === 'function' && bailout(getState())) - ) { return; } - } catch (e) { - return next( - await actionWith( - { - ...requestType, - payload: new RequestError('[RSAA].bailout function failed'), - error: true - }, - [action, getState()] - ) + + // Parse the validated RSAA action + const callAPI = action[RSAA]; + var { endpoint, headers, options = {} } = callAPI; + const { method, body, credentials, bailout, types } = callAPI; + const [requestType, successType, failureType] = normalizeTypeDescriptors( + types ); - } - // Process [RSAA].endpoint function - if (typeof endpoint === 'function') { + // Should we bail out? try { - endpoint = endpoint(getState()); + if ( + (typeof bailout === 'boolean' && bailout) || + (typeof bailout === 'function' && bailout(getState())) + ) { + return; + } } catch (e) { return next( await actionWith( { ...requestType, - payload: new RequestError('[RSAA].endpoint function failed'), + payload: new RequestError('[RSAA].bailout function failed'), error: true }, [action, getState()] ) ); } - } - // Process [RSAA].headers function - if (typeof headers === 'function') { + // Process [RSAA].endpoint function + if (typeof endpoint === 'function') { + try { + endpoint = endpoint(getState()); + } catch (e) { + return next( + await actionWith( + { + ...requestType, + payload: new RequestError('[RSAA].endpoint function failed'), + error: true + }, + [action, getState()] + ) + ); + } + } + + // Process [RSAA].headers function + if (typeof headers === 'function') { + try { + headers = headers(getState()); + } catch (e) { + return next( + await actionWith( + { + ...requestType, + payload: new RequestError('[RSAA].headers function failed'), + error: true + }, + [action, getState()] + ) + ); + } + } + + // Process [RSAA].options function + if (typeof options === 'function') { + try { + options = options(getState()); + } catch (e) { + return next( + await actionWith( + { + ...requestType, + payload: new RequestError('[RSAA].options function failed'), + error: true + }, + [action, getState()] + ) + ); + } + } + + // We can now dispatch the request FSA + if ( + typeof requestType.payload === 'function' || + typeof requestType.meta === 'function' + ) { + next(await actionWith(requestType, [action, getState()])); + } else { + next(requestType); + } + try { - headers = headers(getState()); + // Make the API call + var res = await fetch(endpoint, { + ...options, + method, + body, + credentials, + headers: headers || {} + }); } catch (e) { + // The request was malformed, or there was a network error return next( await actionWith( { ...requestType, - payload: new RequestError('[RSAA].headers function failed'), + payload: new RequestError(e.message), error: true }, [action, getState()] ) ); } - } - // Process [RSAA].options function - if (typeof options === 'function') { - try { - options = options(getState()); - } catch (e) { + // Process the server response + if (res.ok) { + return next(await actionWith(successType, [action, getState(), res])); + } else { return next( await actionWith( { - ...requestType, - payload: new RequestError('[RSAA].options function failed'), + ...failureType, error: true }, - [action, getState()] + [action, getState(), res] ) ); } - } - - // We can now dispatch the request FSA - if ( - typeof requestType.payload === 'function' || - typeof requestType.meta === 'function' - ) { - next(await actionWith(requestType, [action, getState()])); - } else { - next(requestType); - } - - try { - // Make the API call - var res = await fetch(endpoint, { - ...options, - method, - body, - credentials, - headers: headers || {} - }); - } catch (e) { - // The request was malformed, or there was a network error - return next( - await actionWith( - { - ...requestType, - payload: new RequestError(e.message), - error: true - }, - [action, getState()] - ) - ); - } - - // Process the server response - if (res.ok) { - return next(await actionWith(successType, [action, getState(), res])); - } else { - return next( - await actionWith( - { - ...failureType, - error: true - }, - [action, getState(), res] - ) - ); - } + })(); }; } diff --git a/test/index.js b/test/index.js index fe77b9c..691fceb 100644 --- a/test/index.js +++ b/test/index.js @@ -846,6 +846,30 @@ test('apiMiddleware must pass actions without an [RSAA] property to the next han actionHandler(anAction); }); +test("apiMiddleware mustn't return a promise on actions without a [RSAA] property", t => { + const anAction = {}; + const doGetState = () => {}; + const nextHandler = apiMiddleware({ getState: doGetState }); + const doNext = action => action; + const actionHandler = nextHandler(doNext); + + t.plan(1); + const noProm = actionHandler(anAction); + t.notEqual(typeof noProm.then, 'function', 'no promise returned'); +}); + +test('apiMiddleware must return a promise on actions with a [RSAA] property', t => { + const anAction = { [RSAA]: {} }; + const doGetState = () => {}; + const nextHandler = apiMiddleware({ getState: doGetState }); + const doNext = action => action; + const actionHandler = nextHandler(doNext); + + t.plan(1); + const yesProm = actionHandler(anAction); + t.equal(typeof yesProm.then, 'function', 'promise returned'); +}); + test('apiMiddleware must dispatch an error request FSA for an invalid RSAA with a string request type', t => { const anAction = { [RSAA]: {