xhr.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. import utils from '../utils.js';
  2. import settle from '../core/settle.js';
  3. import transitionalDefaults from '../defaults/transitional.js';
  4. import AxiosError from '../core/AxiosError.js';
  5. import CanceledError from '../cancel/CanceledError.js';
  6. import parseProtocol from '../helpers/parseProtocol.js';
  7. import platform from '../platform/index.js';
  8. import AxiosHeaders from '../core/AxiosHeaders.js';
  9. import { progressEventReducer } from '../helpers/progressEventReducer.js';
  10. import resolveConfig from '../helpers/resolveConfig.js';
  11. const isXHRAdapterSupported = typeof XMLHttpRequest !== 'undefined';
  12. export default isXHRAdapterSupported &&
  13. function (config) {
  14. return new Promise(function dispatchXhrRequest(resolve, reject) {
  15. const _config = resolveConfig(config);
  16. let requestData = _config.data;
  17. const requestHeaders = AxiosHeaders.from(_config.headers).normalize();
  18. let { responseType, onUploadProgress, onDownloadProgress } = _config;
  19. let onCanceled;
  20. let uploadThrottled, downloadThrottled;
  21. let flushUpload, flushDownload;
  22. function done() {
  23. flushUpload && flushUpload(); // flush events
  24. flushDownload && flushDownload(); // flush events
  25. _config.cancelToken && _config.cancelToken.unsubscribe(onCanceled);
  26. _config.signal && _config.signal.removeEventListener('abort', onCanceled);
  27. }
  28. let request = new XMLHttpRequest();
  29. request.open(_config.method.toUpperCase(), _config.url, true);
  30. // Set the request timeout in MS
  31. request.timeout = _config.timeout;
  32. function onloadend() {
  33. if (!request) {
  34. return;
  35. }
  36. // Prepare the response
  37. const responseHeaders = AxiosHeaders.from(
  38. 'getAllResponseHeaders' in request && request.getAllResponseHeaders()
  39. );
  40. const responseData =
  41. !responseType || responseType === 'text' || responseType === 'json'
  42. ? request.responseText
  43. : request.response;
  44. const response = {
  45. data: responseData,
  46. status: request.status,
  47. statusText: request.statusText,
  48. headers: responseHeaders,
  49. config,
  50. request,
  51. };
  52. settle(
  53. function _resolve(value) {
  54. resolve(value);
  55. done();
  56. },
  57. function _reject(err) {
  58. reject(err);
  59. done();
  60. },
  61. response
  62. );
  63. // Clean up request
  64. request = null;
  65. }
  66. if ('onloadend' in request) {
  67. // Use onloadend if available
  68. request.onloadend = onloadend;
  69. } else {
  70. // Listen for ready state to emulate onloadend
  71. request.onreadystatechange = function handleLoad() {
  72. if (!request || request.readyState !== 4) {
  73. return;
  74. }
  75. // The request errored out and we didn't get a response, this will be
  76. // handled by onerror instead
  77. // With one exception: request that using file: protocol, most browsers
  78. // will return status as 0 even though it's a successful request
  79. if (
  80. request.status === 0 &&
  81. !(request.responseURL && request.responseURL.startsWith('file:'))
  82. ) {
  83. return;
  84. }
  85. // readystate handler is calling before onerror or ontimeout handlers,
  86. // so we should call onloadend on the next 'tick'
  87. setTimeout(onloadend);
  88. };
  89. }
  90. // Handle browser request cancellation (as opposed to a manual cancellation)
  91. request.onabort = function handleAbort() {
  92. if (!request) {
  93. return;
  94. }
  95. reject(new AxiosError('Request aborted', AxiosError.ECONNABORTED, config, request));
  96. done();
  97. // Clean up request
  98. request = null;
  99. };
  100. // Handle low level network errors
  101. request.onerror = function handleError(event) {
  102. // Browsers deliver a ProgressEvent in XHR onerror
  103. // (message may be empty; when present, surface it)
  104. // See https://developer.mozilla.org/docs/Web/API/XMLHttpRequest/error_event
  105. const msg = event && event.message ? event.message : 'Network Error';
  106. const err = new AxiosError(msg, AxiosError.ERR_NETWORK, config, request);
  107. // attach the underlying event for consumers who want details
  108. err.event = event || null;
  109. reject(err);
  110. done();
  111. request = null;
  112. };
  113. // Handle timeout
  114. request.ontimeout = function handleTimeout() {
  115. let timeoutErrorMessage = _config.timeout
  116. ? 'timeout of ' + _config.timeout + 'ms exceeded'
  117. : 'timeout exceeded';
  118. const transitional = _config.transitional || transitionalDefaults;
  119. if (_config.timeoutErrorMessage) {
  120. timeoutErrorMessage = _config.timeoutErrorMessage;
  121. }
  122. reject(
  123. new AxiosError(
  124. timeoutErrorMessage,
  125. transitional.clarifyTimeoutError ? AxiosError.ETIMEDOUT : AxiosError.ECONNABORTED,
  126. config,
  127. request
  128. )
  129. );
  130. done();
  131. // Clean up request
  132. request = null;
  133. };
  134. // Remove Content-Type if data is undefined
  135. requestData === undefined && requestHeaders.setContentType(null);
  136. // Add headers to the request
  137. if ('setRequestHeader' in request) {
  138. utils.forEach(requestHeaders.toJSON(), function setRequestHeader(val, key) {
  139. request.setRequestHeader(key, val);
  140. });
  141. }
  142. // Add withCredentials to request if needed
  143. if (!utils.isUndefined(_config.withCredentials)) {
  144. request.withCredentials = !!_config.withCredentials;
  145. }
  146. // Add responseType to request if needed
  147. if (responseType && responseType !== 'json') {
  148. request.responseType = _config.responseType;
  149. }
  150. // Handle progress if needed
  151. if (onDownloadProgress) {
  152. [downloadThrottled, flushDownload] = progressEventReducer(onDownloadProgress, true);
  153. request.addEventListener('progress', downloadThrottled);
  154. }
  155. // Not all browsers support upload events
  156. if (onUploadProgress && request.upload) {
  157. [uploadThrottled, flushUpload] = progressEventReducer(onUploadProgress);
  158. request.upload.addEventListener('progress', uploadThrottled);
  159. request.upload.addEventListener('loadend', flushUpload);
  160. }
  161. if (_config.cancelToken || _config.signal) {
  162. // Handle cancellation
  163. // eslint-disable-next-line func-names
  164. onCanceled = (cancel) => {
  165. if (!request) {
  166. return;
  167. }
  168. reject(!cancel || cancel.type ? new CanceledError(null, config, request) : cancel);
  169. request.abort();
  170. done();
  171. request = null;
  172. };
  173. _config.cancelToken && _config.cancelToken.subscribe(onCanceled);
  174. if (_config.signal) {
  175. _config.signal.aborted
  176. ? onCanceled()
  177. : _config.signal.addEventListener('abort', onCanceled);
  178. }
  179. }
  180. const protocol = parseProtocol(_config.url);
  181. if (protocol && !platform.protocols.includes(protocol)) {
  182. reject(
  183. new AxiosError(
  184. 'Unsupported protocol ' + protocol + ':',
  185. AxiosError.ERR_BAD_REQUEST,
  186. config
  187. )
  188. );
  189. return;
  190. }
  191. // Send the request
  192. request.send(requestData || null);
  193. });
  194. };