-
Notifications
You must be signed in to change notification settings - Fork 34
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(sdk): add abort plugin SDK (#1776)
## Proposed change Add plugin to simplify request canceling
- Loading branch information
Showing
7 changed files
with
200 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import type { FetchCall, FetchPlugin, FetchPluginContext, RequestOptions } from '../core'; | ||
|
||
interface AbortCallbackParameters { | ||
/** URL targeted */ | ||
url: string; | ||
|
||
/** Fetch call options */ | ||
options: RequestInit | RequestOptions; | ||
|
||
// TODO Now supported for all the modern browsers - should become mandatory in @ama-sdk/core@11.0 | ||
/** Abort controller to abort fetch call */ | ||
controller?: AbortController; | ||
} | ||
|
||
const isPromise = (result: boolean | void | Promise<void> | Promise<boolean>): result is (Promise<void> | Promise<boolean>) => { | ||
if (typeof result !== 'object') { | ||
return false; | ||
} | ||
|
||
return true; | ||
}; | ||
|
||
/** | ||
* Abort callback | ||
* Returns `true` to abort a request (or access directly to the controller to cancel fetch request) | ||
* @example Immediate abort on URL match | ||
* ```typescript | ||
* const abortCondition: AbortCallback = ({url}) => url.endsWith('pet'); | ||
* | ||
* const client = new ApiFetchClient( | ||
* { | ||
* basePath: 'https://petstore3.swagger.io/api/v3', | ||
* fetchPlugins: [new AbortFetch(abortCondition)] | ||
* } | ||
* ); | ||
* ``` | ||
* @example Abort on external event | ||
* ```typescript | ||
* import { firstValueFrom } from 'rxjs'; | ||
* import { myObservable } from 'somewhere'; | ||
* | ||
* const abortCondition: AbortCallback = ((observable: any) => () => firstValueFrom(observable).then((value) => !!value))(myObservable); | ||
* | ||
* const client = new ApiFetchClient( | ||
* { | ||
* basePath: 'https://petstore3.swagger.io/api/v3', | ||
* fetchPlugins: [new AbortFetch(abortCondition)] | ||
* } | ||
* ); | ||
* ``` | ||
* | ||
* @example Abort on Timeout | ||
* ```typescript | ||
* const abortCondition: AbortCallback = ({controller}) => { setTimeout(() => controller?.abort(), 3000); }; | ||
* | ||
* const client = new ApiFetchClient( | ||
* { | ||
* basePath: 'https://petstore3.swagger.io/api/v3', | ||
* fetchPlugins: [new AbortFetch(abortCondition)] | ||
* } | ||
* ); | ||
* ``` | ||
*/ | ||
export type AbortCallback = (controller?: AbortCallbackParameters) => void | boolean | Promise<void> | Promise<boolean>; | ||
|
||
/** Plugin to abort a Fetch request */ | ||
export class AbortFetch implements FetchPlugin { | ||
|
||
/** | ||
* Abort Fetch plugin | ||
* @param abortCallback Condition that should be passed to start the call | ||
*/ | ||
constructor(public abortCallback: AbortCallback) { | ||
} | ||
|
||
|
||
/** @inheritDoc */ | ||
public load(context: FetchPluginContext) { | ||
return { | ||
transform: (fetchCall: FetchCall) => { | ||
const abortCallbackResult = this.abortCallback(); | ||
if (isPromise(abortCallbackResult)) { | ||
void abortCallbackResult.then((res) => res && context.controller?.abort()); | ||
} else if (abortCallbackResult) { | ||
context.controller?.abort(); | ||
} | ||
return fetchCall; | ||
} | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import { AbortFetch } from './abort.fetch'; | ||
|
||
describe('Abort Plugin', () => { | ||
|
||
it('should trigger the callback', async () => { | ||
const fn = jest.fn(); | ||
const plugin = new AbortFetch(fn); | ||
|
||
const runner = plugin.load({} as any); | ||
await runner.transform(Promise.resolve() as Promise<any>); | ||
|
||
expect(fn).toHaveBeenCalled(); | ||
}); | ||
|
||
it('should trigger abort signal if true', async () => { | ||
const defaultContext = { | ||
controller: { | ||
abort: jest.fn() | ||
} | ||
}; | ||
const fn = jest.fn().mockResolvedValue(true); | ||
const plugin = new AbortFetch(fn); | ||
|
||
const runner = plugin.load(defaultContext as any); | ||
await runner.transform(Promise.resolve() as Promise<any>); | ||
|
||
expect(defaultContext.controller.abort).toHaveBeenCalled(); | ||
}); | ||
|
||
it('should not trigger abort signal if false', async () => { | ||
const defaultContext = { | ||
controller: { | ||
abort: jest.fn() | ||
} | ||
}; | ||
const fn = jest.fn().mockResolvedValue(false); | ||
const plugin = new AbortFetch(fn); | ||
|
||
const runner = plugin.load(defaultContext as any); | ||
await runner.transform(Promise.resolve() as Promise<any>); | ||
|
||
expect(defaultContext.controller.abort).not.toHaveBeenCalled(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './abort.fetch'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
## Abort | ||
|
||
Plugin to abort a Fetch call. | ||
|
||
### Usage examples | ||
|
||
### Immediate abort on URL match | ||
|
||
```typescript | ||
import { AbortFetch, type AbortCallback } from '@ama-sdk/core'; | ||
|
||
const abortCondition: AbortCallback = ({url}) => url.endsWith('pet'); | ||
|
||
const client = new ApiFetchClient( | ||
{ | ||
basePath: 'https://petstore3.swagger.io/api/v3', | ||
fetchPlugins: [new AbortFetch(abortCondition)] | ||
} | ||
); | ||
``` | ||
|
||
### Abort on external event | ||
|
||
```typescript | ||
import { AbortFetch, type AbortCallback } from '@ama-sdk/core'; | ||
import { firstValueFrom } from 'rxjs'; | ||
import { myObservable } from 'somewhere'; | ||
|
||
const abortCondition: AbortCallback = ((observable: any) => () => firstValueFrom(observable).then((value) => !!value))(myObservable); | ||
|
||
const client = new ApiFetchClient( | ||
{ | ||
basePath: 'https://petstore3.swagger.io/api/v3', | ||
fetchPlugins: [new AbortFetch(abortCondition)] | ||
} | ||
); | ||
``` | ||
|
||
### Abort on Timeout | ||
|
||
```typescript | ||
import { AbortFetch, type AbortCallback } from '@ama-sdk/core'; | ||
|
||
const abortCondition: AbortCallback = ({controller}) => { setTimeout(() => controller?.abort(), 3000); }; | ||
|
||
const client = new ApiFetchClient( | ||
{ | ||
basePath: 'https://petstore3.swagger.io/api/v3', | ||
fetchPlugins: [new AbortFetch(abortCondition)] | ||
} | ||
); | ||
``` | ||
|
||
> [!WARN] | ||
> We recommend to use the [Timeout plugin](https://github.com/AmadeusITGroup/otter/tree/main/packages/%40ama-sdk/core/src/plugins/timeout) to implement more easily and properly a request timeout. | ||
### Type of plugins | ||
|
||
- Fetch plugin: [AbortFetch](./abort.fetch.ts) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters