import { Observable } from '../../Observable';
|
|
/**
|
* Uses [the Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) to
|
* make an HTTP request.
|
*
|
* **WARNING** Parts of the fetch API are still experimental. `AbortController` is
|
* required for this implementation to work and use cancellation appropriately.
|
*
|
* Will automatically set up an internal [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController)
|
* in order to teardown the internal `fetch` when the subscription tears down.
|
*
|
* If a `signal` is provided via the `init` argument, it will behave like it usually does with
|
* `fetch`. If the provided `signal` aborts, the error that `fetch` normally rejects with
|
* in that scenario will be emitted as an error from the observable.
|
*
|
* ### Basic Use
|
*
|
* ```ts
|
* import { of } from 'rxjs';
|
* import { fromFetch } from 'rxjs/fetch';
|
* import { switchMap, catchError } from 'rxjs/operators';
|
*
|
* const data$ = fromFetch('https://api.github.com/users?per_page=5').pipe(
|
* switchMap(response => {
|
* if (response.ok) {
|
* // OK return data
|
* return response.json();
|
* } else {
|
* // Server is returning a status requiring the client to try something else.
|
* return of({ error: true, message: `Error ${response.status}` });
|
* }
|
* }),
|
* catchError(err => {
|
* // Network or other error, handle appropriately
|
* console.error(err);
|
* return of({ error: true, message: err.message })
|
* })
|
* );
|
*
|
* data$.subscribe({
|
* next: result => console.log(result),
|
* complete: () => console.log('done')
|
* })
|
* ```
|
*
|
* @param input The resource you would like to fetch. Can be a url or a request object.
|
* @param init A configuration object for the fetch.
|
* [See MDN for more details](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters)
|
* @returns An Observable, that when subscribed to performs an HTTP request using the native `fetch`
|
* function. The {@link Subscription} is tied to an `AbortController` for the the fetch.
|
*/
|
export function fromFetch(input: string | Request, init?: RequestInit): Observable<Response> {
|
return new Observable<Response>(subscriber => {
|
const controller = new AbortController();
|
const signal = controller.signal;
|
let outerSignalHandler: () => void;
|
let abortable = true;
|
let unsubscribed = false;
|
|
if (init) {
|
// If a signal is provided, just have it teardown. It's a cancellation token, basically.
|
if (init.signal) {
|
outerSignalHandler = () => {
|
if (!signal.aborted) {
|
controller.abort();
|
}
|
};
|
init.signal.addEventListener('abort', outerSignalHandler);
|
}
|
init.signal = signal;
|
} else {
|
init = { signal };
|
}
|
|
fetch(input, init).then(response => {
|
abortable = false;
|
subscriber.next(response);
|
subscriber.complete();
|
}).catch(err => {
|
abortable = false;
|
if (!unsubscribed) {
|
// Only forward the error if it wasn't an abort.
|
subscriber.error(err);
|
}
|
});
|
|
return () => {
|
unsubscribed = true;
|
if (abortable) {
|
controller.abort();
|
}
|
};
|
});
|
}
|