-
Notifications
You must be signed in to change notification settings - Fork 4.7k
API Fetch: Support custom fetch handlers #12365
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d0c0c4e
5d1a92f
7d0f115
8c10f7c
13c7088
27c951d
3a285de
4422de2
ecdac11
9b9424b
5bce8a6
a5bbeec
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,106 +38,121 @@ const DEFAULT_OPTIONS = { | |
| credentials: 'include', | ||
| }; | ||
|
|
||
| const middlewares = []; | ||
| const middlewares = [ | ||
| userLocaleMiddleware, | ||
| namespaceEndpointMiddleware, | ||
| httpV1Middleware, | ||
| fetchAllMiddleware, | ||
| ]; | ||
|
|
||
| function registerMiddleware( middleware ) { | ||
| middlewares.push( middleware ); | ||
| middlewares.unshift( middleware ); | ||
| } | ||
|
|
||
| function apiFetch( options ) { | ||
| const raw = ( nextOptions ) => { | ||
| const { url, path, data, parse = true, ...remainingOptions } = nextOptions; | ||
| let { body, headers } = nextOptions; | ||
| const defaultFetchHandler = ( nextOptions ) => { | ||
| const { url, path, data, parse = true, ...remainingOptions } = nextOptions; | ||
| let { body, headers } = nextOptions; | ||
|
|
||
| // Merge explicitly-provided headers with default values. | ||
| headers = { ...DEFAULT_HEADERS, ...headers }; | ||
|
|
||
| // The `data` property is a shorthand for sending a JSON body. | ||
| if ( data ) { | ||
| body = JSON.stringify( data ); | ||
| headers[ 'Content-Type' ] = 'application/json'; | ||
| } | ||
|
|
||
| const responsePromise = window.fetch( | ||
| url || path, | ||
| { | ||
| ...DEFAULT_OPTIONS, | ||
| ...remainingOptions, | ||
| body, | ||
| headers, | ||
| } | ||
| ); | ||
| const checkStatus = ( response ) => { | ||
| if ( response.status >= 200 && response.status < 300 ) { | ||
| return response; | ||
| } | ||
|
|
||
| throw response; | ||
| }; | ||
|
|
||
| // Merge explicitly-provided headers with default values. | ||
| headers = { ...DEFAULT_HEADERS, ...headers }; | ||
| const parseResponse = ( response ) => { | ||
| if ( parse ) { | ||
| if ( response.status === 204 ) { | ||
| return null; | ||
| } | ||
|
|
||
| // The `data` property is a shorthand for sending a JSON body. | ||
| if ( data ) { | ||
| body = JSON.stringify( data ); | ||
| headers[ 'Content-Type' ] = 'application/json'; | ||
| return response.json ? response.json() : Promise.reject( response ); | ||
| } | ||
|
|
||
| const responsePromise = window.fetch( | ||
| url || path, | ||
| { | ||
| ...DEFAULT_OPTIONS, | ||
| ...remainingOptions, | ||
| body, | ||
| headers, | ||
| } | ||
| ); | ||
| const checkStatus = ( response ) => { | ||
| if ( response.status >= 200 && response.status < 300 ) { | ||
| return response; | ||
| } | ||
| return response; | ||
| }; | ||
|
|
||
| throw response; | ||
| }; | ||
| return responsePromise | ||
| .then( checkStatus ) | ||
| .then( parseResponse ) | ||
| .catch( ( response ) => { | ||
| if ( ! parse ) { | ||
| throw response; | ||
| } | ||
|
|
||
| const parseResponse = ( response ) => { | ||
| if ( parse ) { | ||
| if ( response.status === 204 ) { | ||
| return null; | ||
| } | ||
| const invalidJsonError = { | ||
| code: 'invalid_json', | ||
| message: __( 'The response is not a valid JSON response.' ), | ||
| }; | ||
|
|
||
| return response.json ? response.json() : Promise.reject( response ); | ||
| if ( ! response || ! response.json ) { | ||
| throw invalidJsonError; | ||
| } | ||
|
|
||
| return response; | ||
| }; | ||
|
|
||
| return responsePromise | ||
| .then( checkStatus ) | ||
| .then( parseResponse ) | ||
| .catch( ( response ) => { | ||
| if ( ! parse ) { | ||
| throw response; | ||
| } | ||
|
|
||
| const invalidJsonError = { | ||
| code: 'invalid_json', | ||
| message: __( 'The response is not a valid JSON response.' ), | ||
| }; | ||
|
|
||
| if ( ! response || ! response.json ) { | ||
| return response.json() | ||
| .catch( () => { | ||
| throw invalidJsonError; | ||
| } | ||
|
|
||
| return response.json() | ||
| .catch( () => { | ||
| throw invalidJsonError; | ||
| } ) | ||
| .then( ( error ) => { | ||
| const unknownError = { | ||
| code: 'unknown_error', | ||
| message: __( 'An unknown error occurred.' ), | ||
| }; | ||
|
|
||
| throw error || unknownError; | ||
| } ); | ||
| } ); | ||
| }; | ||
| } ) | ||
| .then( ( error ) => { | ||
| const unknownError = { | ||
| code: 'unknown_error', | ||
| message: __( 'An unknown error occurred.' ), | ||
| }; | ||
|
|
||
| throw error || unknownError; | ||
| } ); | ||
| } ); | ||
| }; | ||
|
|
||
| let fetchHandler = defaultFetchHandler; | ||
|
|
||
| /** | ||
| * Defines a custom fetch handler for making the requests that will override | ||
| * the default one using window.fetch | ||
| * | ||
| * @param {Function} newFetchHandler The new fetch handler | ||
| */ | ||
| function setFetchHandler( newFetchHandler ) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know this file was already lacking so JSDocs, but maybe a good time to start adding those.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added in e2dc3d3 |
||
| fetchHandler = newFetchHandler; | ||
| } | ||
|
|
||
| function apiFetch( options ) { | ||
| const steps = [ ...middlewares, fetchHandler ]; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for updating this code here. It is now clear that |
||
|
|
||
| const createRunStep = ( index ) => ( workingOptions ) => { | ||
| const step = steps[ index ]; | ||
| if ( index === steps.length - 1 ) { | ||
| return step( workingOptions ); | ||
| } | ||
|
|
||
| const steps = [ | ||
| raw, | ||
| fetchAllMiddleware, | ||
| httpV1Middleware, | ||
| namespaceEndpointMiddleware, | ||
| userLocaleMiddleware, | ||
| ...middlewares, | ||
| ].reverse(); | ||
|
|
||
| const runMiddleware = ( index ) => ( nextOptions ) => { | ||
| const nextMiddleware = steps[ index ]; | ||
| const next = runMiddleware( index + 1 ); | ||
| return nextMiddleware( nextOptions, next ); | ||
| const next = createRunStep( index + 1 ); | ||
| return step( workingOptions, next ); | ||
| }; | ||
|
|
||
| return runMiddleware( 0 )( options ); | ||
| return createRunStep( 0 )( options ); | ||
| } | ||
|
|
||
| apiFetch.use = registerMiddleware; | ||
| apiFetch.setFetchHandler = setFetchHandler; | ||
|
|
||
| apiFetch.createNonceMiddleware = createNonceMiddleware; | ||
| apiFetch.createPreloadingMiddleware = createPreloadingMiddleware; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, much easier to follow than using reverse on the list of middlewares 👍