fetchWithRetries.js.flow 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. /**
  2. * Copyright (c) 2013-present, Facebook, Inc.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. *
  7. * @providesModule fetchWithRetries
  8. * @typechecks
  9. * @flow
  10. */
  11. 'use strict';
  12. const ExecutionEnvironment = require('./ExecutionEnvironment');
  13. const sprintf = require('./sprintf');
  14. const fetch = require('./fetch');
  15. const warning = require('./warning');
  16. export type InitWithRetries = {
  17. body?: mixed;
  18. cache?: ?string;
  19. credentials?: ?string;
  20. fetchTimeout?: ?number;
  21. headers?: mixed;
  22. method?: ?string;
  23. mode?: ?string;
  24. retryDelays?: ?Array<number>;
  25. };
  26. const DEFAULT_TIMEOUT = 15000;
  27. const DEFAULT_RETRIES = [1000, 3000];
  28. /**
  29. * Makes a POST request to the server with the given data as the payload.
  30. * Automatic retries are done based on the values in `retryDelays`.
  31. */
  32. function fetchWithRetries(uri: string, initWithRetries?: ?InitWithRetries): Promise<any> {
  33. const { fetchTimeout, retryDelays, ...init } = initWithRetries || {};
  34. const _fetchTimeout = fetchTimeout != null ? fetchTimeout : DEFAULT_TIMEOUT;
  35. const _retryDelays = retryDelays != null ? retryDelays : DEFAULT_RETRIES;
  36. let requestsAttempted = 0;
  37. let requestStartTime = 0;
  38. return new Promise((resolve, reject) => {
  39. /**
  40. * Sends a request to the server that will timeout after `fetchTimeout`.
  41. * If the request fails or times out a new request might be scheduled.
  42. */
  43. function sendTimedRequest(): void {
  44. requestsAttempted++;
  45. requestStartTime = Date.now();
  46. let isRequestAlive = true;
  47. const request = fetch(uri, init);
  48. const requestTimeout = setTimeout(() => {
  49. isRequestAlive = false;
  50. if (shouldRetry(requestsAttempted)) {
  51. warning(false, 'fetchWithRetries: HTTP timeout, retrying.');
  52. retryRequest();
  53. } else {
  54. reject(new Error(sprintf('fetchWithRetries(): Failed to get response from server, ' + 'tried %s times.', requestsAttempted)));
  55. }
  56. }, _fetchTimeout);
  57. request.then(response => {
  58. clearTimeout(requestTimeout);
  59. if (isRequestAlive) {
  60. // We got a response, we can clear the timeout.
  61. if (response.status >= 200 && response.status < 300) {
  62. // Got a response code that indicates success, resolve the promise.
  63. resolve(response);
  64. } else if (shouldRetry(requestsAttempted)) {
  65. // Fetch was not successful, retrying.
  66. // TODO(#7595849): Only retry on transient HTTP errors.
  67. warning(false, 'fetchWithRetries: HTTP error, retrying.'), retryRequest();
  68. } else {
  69. // Request was not successful, giving up.
  70. const error: any = new Error(sprintf('fetchWithRetries(): Still no successful response after ' + '%s retries, giving up.', requestsAttempted));
  71. error.response = response;
  72. reject(error);
  73. }
  74. }
  75. }).catch(error => {
  76. clearTimeout(requestTimeout);
  77. if (shouldRetry(requestsAttempted)) {
  78. retryRequest();
  79. } else {
  80. reject(error);
  81. }
  82. });
  83. }
  84. /**
  85. * Schedules another run of sendTimedRequest based on how much time has
  86. * passed between the time the last request was sent and now.
  87. */
  88. function retryRequest(): void {
  89. const retryDelay = _retryDelays[requestsAttempted - 1];
  90. const retryStartTime = requestStartTime + retryDelay;
  91. // Schedule retry for a configured duration after last request started.
  92. setTimeout(sendTimedRequest, retryStartTime - Date.now());
  93. }
  94. /**
  95. * Checks if another attempt should be done to send a request to the server.
  96. */
  97. function shouldRetry(attempt: number): boolean {
  98. return ExecutionEnvironment.canUseDOM && attempt <= _retryDelays.length;
  99. }
  100. sendTimedRequest();
  101. });
  102. }
  103. module.exports = fetchWithRetries;