Skip to content

Commit

Permalink
Fix abort error thrown by api-fetch and add documentation (#32530)
Browse files Browse the repository at this point in the history
Co-authored-by: Dave Smith <[email protected]>
  • Loading branch information
kevin940726 and getdave authored Jun 17, 2021
1 parent f12865f commit 257ba1e
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 22 deletions.
4 changes: 4 additions & 0 deletions packages/api-fetch/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### New feature

- `AbortError` being thrown by the default fetch handler can now be caught and handled separately in user-land. Add documentation about aborting a request ([#32530](https://github.com/WordPress/gutenberg/pull/32530)).

## 5.1.0 (2021-05-20)

## 5.0.0 (2021-05-14)
Expand Down
28 changes: 28 additions & 0 deletions packages/api-fetch/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,34 @@ Unlike `fetch`, the `Promise` return value of `apiFetch` will resolve to the par

Shorthand to be used in place of `body`, accepts an object value to be stringified to JSON.

### Aborting a request

Aborting a request can be achieved through the use of [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) in the same way as you would when using the native `fetch` API.

For legacy browsers that don't support `AbortController`, you can either:

* Provide your own polyfill of `AbortController` if you still want it to be abortable.
* Ignore it as shown in the example below.

**Example**

```js
const controller = typeof AbortController === 'undefined'
? undefined
: new AbortController();

apiFetch( { path: '/wp/v2/posts', signal: controller?.signal } )
.catch( ( error ) => {
// If the browser doesn't support AbortController then the code below will never log.
// However, in most cases this should be fine as it can be considered to be a progressive enhancement.
if ( error.name === 'AbortError' ) {
console.log( 'Request has been aborted' );
}
} );

controller?.abort();
```
### Middlewares
the `api-fetch` package supports middlewares. Middlewares are functions you can use to wrap the `apiFetch` calls to perform any pre/post process to the API requests.
Expand Down
43 changes: 21 additions & 22 deletions packages/api-fetch/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,28 +109,27 @@ const defaultFetchHandler = ( nextOptions ) => {
}
);

return (
responsePromise
// Return early if fetch errors. If fetch error, there is most likely no
// network connection. Unfortunately fetch just throws a TypeError and
// the message might depend on the browser.
.then(
( value ) =>
Promise.resolve( value )
.then( checkStatus )
.catch( ( response ) =>
parseAndThrowError( response, parse )
)
.then( ( response ) =>
parseResponseAndNormalizeError( response, parse )
),
() => {
throw {
code: 'fetch_error',
message: __( 'You are probably offline.' ),
};
}
)
return responsePromise.then(
( value ) =>
Promise.resolve( value )
.then( checkStatus )
.catch( ( response ) => parseAndThrowError( response, parse ) )
.then( ( response ) =>
parseResponseAndNormalizeError( response, parse )
),
( err ) => {
// Re-throw AbortError for the users to handle it themselves.
if ( err && err.name === 'AbortError' ) {
throw err;
}

// Otherwise, there is most likely no network connection.
// Unfortunately the message might depend on the browser.
throw {
code: 'fetch_error',
message: __( 'You are probably offline.' ),
};
}
);
};

Expand Down
27 changes: 27 additions & 0 deletions packages/api-fetch/src/test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,33 @@ describe( 'apiFetch', () => {
} );
} );

it( 'should throw AbortError when fetch aborts', async () => {
const abortError = new Error();
abortError.name = 'AbortError';
abortError.code = 20;

window.fetch.mockReturnValue( Promise.reject( abortError ) );

const controller = new window.AbortController();

const promise = apiFetch( {
path: '/random',
signal: controller.signal,
} );

controller.abort();

let error;

try {
await promise;
} catch ( err ) {
error = err;
}

expect( error.name ).toBe( 'AbortError' );
} );

it( 'should return null if response has no content status code', () => {
window.fetch.mockReturnValue(
Promise.resolve( {
Expand Down

0 comments on commit 257ba1e

Please sign in to comment.