Repositorio del curso CCOM4030 el semestre B91 del proyecto Artesanías con el Instituto de Cultura

fetch.ts 5.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. import { Observable } from '../../Observable';
  2. import { Subscription } from '../../Subscription';
  3. import { from } from '../../observable/from';
  4. import { ObservableInput } from '../../types';
  5. export function fromFetch<T>(
  6. input: string | Request,
  7. init: RequestInit & {
  8. selector: (response: Response) => ObservableInput<T>
  9. }
  10. ): Observable<T>;
  11. export function fromFetch(
  12. input: string | Request,
  13. init?: RequestInit
  14. ): Observable<Response>;
  15. /**
  16. * Uses [the Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) to
  17. * make an HTTP request.
  18. *
  19. * **WARNING** Parts of the fetch API are still experimental. `AbortController` is
  20. * required for this implementation to work and use cancellation appropriately.
  21. *
  22. * Will automatically set up an internal [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController)
  23. * in order to teardown the internal `fetch` when the subscription tears down.
  24. *
  25. * If a `signal` is provided via the `init` argument, it will behave like it usually does with
  26. * `fetch`. If the provided `signal` aborts, the error that `fetch` normally rejects with
  27. * in that scenario will be emitted as an error from the observable.
  28. *
  29. * ### Basic Use
  30. *
  31. * ```ts
  32. * import { of } from 'rxjs';
  33. * import { fromFetch } from 'rxjs/fetch';
  34. * import { switchMap, catchError } from 'rxjs/operators';
  35. *
  36. * const data$ = fromFetch('https://api.github.com/users?per_page=5').pipe(
  37. * switchMap(response => {
  38. * if (response.ok) {
  39. * // OK return data
  40. * return response.json();
  41. * } else {
  42. * // Server is returning a status requiring the client to try something else.
  43. * return of({ error: true, message: `Error ${response.status}` });
  44. * }
  45. * }),
  46. * catchError(err => {
  47. * // Network or other error, handle appropriately
  48. * console.error(err);
  49. * return of({ error: true, message: err.message })
  50. * })
  51. * );
  52. *
  53. * data$.subscribe({
  54. * next: result => console.log(result),
  55. * complete: () => console.log('done')
  56. * });
  57. * ```
  58. *
  59. * ### Use with Chunked Transfer Encoding
  60. *
  61. * With HTTP responses that use [chunked transfer encoding](https://tools.ietf.org/html/rfc7230#section-3.3.1),
  62. * the promise returned by `fetch` will resolve as soon as the response's headers are
  63. * received.
  64. *
  65. * That means the `fromFetch` observable will emit a `Response` - and will
  66. * then complete - before the body is received. When one of the methods on the
  67. * `Response` - like `text()` or `json()` - is called, the returned promise will not
  68. * resolve until the entire body has been received. Unsubscribing from any observable
  69. * that uses the promise as an observable input will not abort the request.
  70. *
  71. * To facilitate aborting the retrieval of responses that use chunked transfer encoding,
  72. * a `selector` can be specified via the `init` parameter:
  73. *
  74. * ```ts
  75. * import { of } from 'rxjs';
  76. * import { fromFetch } from 'rxjs/fetch';
  77. *
  78. * const data$ = fromFetch('https://api.github.com/users?per_page=5', {
  79. * selector: response => response.json()
  80. * });
  81. *
  82. * data$.subscribe({
  83. * next: result => console.log(result),
  84. * complete: () => console.log('done')
  85. * });
  86. * ```
  87. *
  88. * @param input The resource you would like to fetch. Can be a url or a request object.
  89. * @param init A configuration object for the fetch.
  90. * [See MDN for more details](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters)
  91. * @returns An Observable, that when subscribed to performs an HTTP request using the native `fetch`
  92. * function. The {@link Subscription} is tied to an `AbortController` for the the fetch.
  93. */
  94. export function fromFetch<T>(
  95. input: string | Request,
  96. initWithSelector: RequestInit & {
  97. selector?: (response: Response) => ObservableInput<T>
  98. } = {}
  99. ): Observable<Response | T> {
  100. const { selector, ...init } = initWithSelector;
  101. return new Observable<Response | T>(subscriber => {
  102. const controller = new AbortController();
  103. const signal = controller.signal;
  104. let abortable = true;
  105. let unsubscribed = false;
  106. const subscription = new Subscription();
  107. subscription.add(() => {
  108. unsubscribed = true;
  109. if (abortable) {
  110. controller.abort();
  111. }
  112. });
  113. let perSubscriberInit: RequestInit;
  114. if (init) {
  115. // If a signal is provided, just have it teardown. It's a cancellation token, basically.
  116. if (init.signal) {
  117. if (init.signal.aborted) {
  118. controller.abort();
  119. } else {
  120. const outerSignal = init.signal;
  121. const outerSignalHandler = () => {
  122. if (!signal.aborted) {
  123. controller.abort();
  124. }
  125. };
  126. outerSignal.addEventListener('abort', outerSignalHandler);
  127. subscription.add(() => outerSignal.removeEventListener('abort', outerSignalHandler));
  128. }
  129. }
  130. // init cannot be mutated or reassigned as it's closed over by the
  131. // subscriber callback and is shared between subscribers.
  132. perSubscriberInit = { ...init, signal };
  133. } else {
  134. perSubscriberInit = { signal };
  135. }
  136. fetch(input, perSubscriberInit).then(response => {
  137. if (selector) {
  138. subscription.add(from(selector(response)).subscribe(
  139. value => subscriber.next(value),
  140. err => {
  141. abortable = false;
  142. if (!unsubscribed) {
  143. // Only forward the error if it wasn't an abort.
  144. subscriber.error(err);
  145. }
  146. },
  147. () => {
  148. abortable = false;
  149. subscriber.complete();
  150. }
  151. ));
  152. } else {
  153. abortable = false;
  154. subscriber.next(response);
  155. subscriber.complete();
  156. }
  157. }).catch(err => {
  158. abortable = false;
  159. if (!unsubscribed) {
  160. // Only forward the error if it wasn't an abort.
  161. subscriber.error(err);
  162. }
  163. });
  164. return subscription;
  165. });
  166. }