diff --git a/README.md b/README.md index d7a461c..e93b993 100644 --- a/README.md +++ b/README.md @@ -182,26 +182,32 @@ The `[CALL_API].types` property controls the output of `redux-api-middleware`. T 1. When `redux-api-middleware` receives an action, it first checks whether it has a `[CALL_API]` property. If it does not, it was clearly not intended for processing with `redux-api-middleware`, and so it is unceremoniously passed on to the next middleware. 2. It is now time to validate the action against the [RSAA definition](#redux-standard-api-calling-actions). If there are any validation errors, a *request* FSA will be dispatched (if at all possible) with the following properties: - - `type`: the string constant in the first position of the `[CALL_API].types` array; - - `payload`: an [`InvalidRSAA`](#invalidrsaa) object containing a list of said validation errors; - - `error: true`. + - `type`: the string constant in the first position of the `[CALL_API].types` array; + - `payload`: an [`InvalidRSAA`](#invalidrsaa) object containing a list of said validation errors; + - `error: true`. `redux-api-middleware` will perform no further operations. In particular, no API call will be made, and the incoming RSAA will die here. -3. Now that `redux-api-middleware` is sure it has received a valid RSAA, it will try making the API call. If everything is alright, a *request* FSA will be dispatched with the following property: +3. Next, `redux-api-middleware` prepares the API call and has to call those of `[CALL_API].bailout`, `[CALL_API].endpoint` and `[CALL_API].headers` that happen to be a function. If any of these throw an error, a *request* FSA will be dispatched with the following properties: + - `type`: the string constant in the first position of the `[CALL_API].types` array; + - `payload`: a [`RequestError`](#requesterror) object containing an error message; + - `error: true`. + + Processing stops here and `redux-api-middleware` will perform no further operations. In particular, no API call will be made, and the incoming RSAA will die here. + +4. Now that `redux-api-middleware` is sure it has received a valid RSAA, and has successfully prepared the API call, it will try making the API call. A *request* FSA will be dispatched with the following property: - `type`: the string constant in the first position of the `[CALL_API].types` array. But errors may pop up at this stage, for several reasons: - - `redux-api-middleware` has to call those of `[CALL_API].bailout`, `[CALL_API].endpoint` and `[CALL_API].headers` that happen to be a function, which may throw an error; - `isomorphic-fetch` may throw an error: the RSAA definition is not strong enough to preclude that from happening (you may, for example, send in a `[CALL_API].body` that is not valid according to the fetch specification — mind the SHOULDs in the [RSAA definition](#redux-standard-api-calling-actions)); - a network failure occurs (the network is unreachable, the server responds with an error,...). - If such an error occurs, a different *request* FSA will be dispatched (*instead* of the one described above). It will contain the following properties: - - `type`: the string constant in the first position of the `[CALL_API].types` array; + If such an error occurs, a *failure* FSA will be dispatched (after the *request* FSA described above) and processing stops here. The FSA will contain the following properties: + - `type`: the string constant in the third position of the `[CALL_API].types` array; - `payload`: a [`RequestError`](#requesterror) object containing an error message; - `error: true`. -4. If `redux-api-middleware` receives a response from the server with a status code in the 200 range, a *success* FSA will be dispatched with the following properties: +5. If `redux-api-middleware` receives a response from the server with a status code in the 200 range, a *success* FSA will be dispatched with the following properties: - `type`: the string constant in the second position of the `[CALL_API].types` array; - `payload`: if the `Content-Type` header of the response is set to something JSONy (see [*Success* type descriptors](#success-type-descriptors) below), the parsed JSON response of the server, or undefined otherwise. @@ -282,7 +288,7 @@ By default, *request* FSAs will not contain `payload` and `meta` properties. Error *request* FSAs might need to obviate these custom settings though. - *Request* FSAs resulting from invalid RSAAs (step 2 in [Lifecycle](#lifecycle) above) cannot be customized. `redux-api-middleware` will try to dispatch an error *request* FSA, but it might not be able to (it may happen that the invalid RSAA does not contain a value that can be used as the *request* FSA `type` property, in which case `redux-api-middleware` will let the RSAA die silently). - - *Request* FSAs resulting in request errors (step 3 in [Lifecycle](#lifecycle) above) will honor the user-provided `meta`, but will ignore the user-provided `payload`, which is reserved for the default error object. + - *Request* FSAs resulting from errors while preparing the API call (step 3 in [Lifecycle](#lifecycle) above) will honor the user-provided `meta`, but will ignore the user-provided `payload`, which is reserved for the default error object. #### *Success* type descriptors @@ -351,7 +357,7 @@ By default, *success* FSAs will not contain a `meta` property, while their `payl #### *Failure* type descriptors -`payload` and `meta` functions will be passed the RSAA action itself, the state of your Redux store, and the raw server response — exactly as for *success* type descriptors. The `error` property of dispatched *failure* FSAs will always be set to `true`. +`payload` and `meta` functions will be passed the RSAA action itself, the state of your Redux store, and the raw server response if available. The `error` property of dispatched *failure* FSAs will always be set to `true`. For example, if you want the status code and status message of a unsuccessful API call in the `meta` property of your *failure* FSA, do the following. @@ -382,6 +388,7 @@ For example, if you want the status code and status message of a unsuccessful AP } } ``` + By default, *failure* FSAs will not contain a `meta` property, while their `payload` property will be evaluated from ```js (action, state, res) => @@ -390,6 +397,9 @@ By default, *failure* FSAs will not contain a `meta` property, while their `payl ) ``` +*Failure* FSAs without a server response need to obviate these custom settings though. + - *Failure* FSAs resulting from network failures or errors thrown by `isomorphic-fetch` (step 4 in [Lifecycle](#lifecycle) above) will honor the user-provided `meta`, but will ignore the user-provided `payload`, which is reserved for the default error object. + ## Reference ### Exports diff --git a/src/middleware.js b/src/middleware.js index 0df4014..854d262 100644 --- a/src/middleware.js +++ b/src/middleware.js @@ -97,6 +97,8 @@ function apiMiddleware({ getState }) { requestType, [action, getState()] )); + // As we have dispatched the request FSA, all other + // dispatches must now be either success or failure try { // Make the API call @@ -105,7 +107,7 @@ function apiMiddleware({ getState }) { // The request was malformed, or there was a network error return next(await actionWith( { - ...requestType, + ...failureType, payload: new RequestError(e.message), error: true }, diff --git a/test/index.js b/test/index.js index 95765ed..f186359 100644 --- a/test/index.js +++ b/test/index.js @@ -1097,11 +1097,15 @@ test('apiMiddleware must dispatch an error request FSA on a request error', (t) types: [ { type: 'REQUEST', - payload: 'ignoredPayload', - meta: 'someMeta' + payload: 'someRequestPayload', + meta: 'someRequestMeta' }, 'SUCCESS', - 'FAILURE' + { + type: 'FAILURE', + payload: 'ignoredFailurePayload', + meta: 'someFailureMeta' + } ] } }; @@ -1110,50 +1114,49 @@ test('apiMiddleware must dispatch an error request FSA on a request error', (t) const doNext = (action) => { switch (action.type) { case 'REQUEST': - if (!action.error) { - t.pass('next handler called'); - t.equal( - action.type, - 'REQUEST', - 'dispatched non-error FSA has correct type property' - ); - t.equal( - action.payload, - 'ignoredPayload', - 'dispatched non-error FSA has correct payload property' - ); - t.equal( - action.meta, - 'someMeta', - 'dispatched non-error FSA has correct meta property' - ); - t.notOk( - action.error, - 'dispatched non-error FSA has correct error property' - ); - break; - } else { - t.pass('next handler called'); - t.equal( - action.type, - 'REQUEST', - 'dispatched error FSA has correct type property' - ); - t.equal( - action.payload.name, - 'RequestError', - 'dispatched error FSA has correct payload property' - ); - t.equal( - action.meta, - 'someMeta', - 'dispatched error FSA has correct meta property' - ); - t.ok( - action.error, - 'dispatched error FSA has correct error property' - ); - } + t.pass('next handler called'); + t.equal( + action.type, + 'REQUEST', + 'dispatched request FSA has correct type property' + ); + t.equal( + action.payload, + 'someRequestPayload', + 'dispatched request FSA has correct payload property' + ); + t.equal( + action.meta, + 'someRequestMeta', + 'dispatched request FSA has correct meta property' + ); + t.notOk( + action.error, + 'dispatched request FSA has correct error property' + ); + break; + case 'FAILURE': + t.pass('next handler called'); + t.equal( + action.type, + 'FAILURE', + 'dispatched failure FSA has correct type property' + ); + t.equal( + action.payload.name, + 'RequestError', + 'dispatched failure FSA has correct payload property' + ); + t.equal( + action.meta, + 'someFailureMeta', + 'dispatched failure FSA has correct meta property' + ); + t.ok( + action.error, + 'dispatched failure FSA has correct error property' + ); + break; } }; const actionHandler = nextHandler(doNext);