xhr.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  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.indexOf('file:') === 0)
  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. // Clean up request
  97. request = null;
  98. };
  99. // Handle low level network errors
  100. request.onerror = function handleError(event) {
  101. // Browsers deliver a ProgressEvent in XHR onerror
  102. // (message may be empty; when present, surface it)
  103. // See https://developer.mozilla.org/docs/Web/API/XMLHttpRequest/error_event
  104. const msg = event && event.message ? event.message : 'Network Error';
  105. const err = new AxiosError(msg, AxiosError.ERR_NETWORK, config, request);
  106. // attach the underlying event for consumers who want details
  107. err.event = event || null;
  108. reject(err);
  109. request = null;
  110. };
  111. // Handle timeout
  112. request.ontimeout = function handleTimeout() {
  113. let timeoutErrorMessage = _config.timeout
  114. ? 'timeout of ' + _config.timeout + 'ms exceeded'
  115. : 'timeout exceeded';
  116. const transitional = _config.transitional || transitionalDefaults;
  117. if (_config.timeoutErrorMessage) {
  118. timeoutErrorMessage = _config.timeoutErrorMessage;
  119. }
  120. reject(
  121. new AxiosError(
  122. timeoutErrorMessage,
  123. transitional.clarifyTimeoutError ? AxiosError.ETIMEDOUT : AxiosError.ECONNABORTED,
  124. config,
  125. request
  126. )
  127. );
  128. // Clean up request
  129. request = null;
  130. };
  131. // Remove Content-Type if data is undefined
  132. requestData === undefined && requestHeaders.setContentType(null);
  133. // Add headers to the request
  134. if ('setRequestHeader' in request) {
  135. utils.forEach(requestHeaders.toJSON(), function setRequestHeader(val, key) {
  136. request.setRequestHeader(key, val);
  137. });
  138. }
  139. // Add withCredentials to request if needed
  140. if (!utils.isUndefined(_config.withCredentials)) {
  141. request.withCredentials = !!_config.withCredentials;
  142. }
  143. // Add responseType to request if needed
  144. if (responseType && responseType !== 'json') {
  145. request.responseType = _config.responseType;
  146. }
  147. // Handle progress if needed
  148. if (onDownloadProgress) {
  149. [downloadThrottled, flushDownload] = progressEventReducer(onDownloadProgress, true);
  150. request.addEventListener('progress', downloadThrottled);
  151. }
  152. // Not all browsers support upload events
  153. if (onUploadProgress && request.upload) {
  154. [uploadThrottled, flushUpload] = progressEventReducer(onUploadProgress);
  155. request.upload.addEventListener('progress', uploadThrottled);
  156. request.upload.addEventListener('loadend', flushUpload);
  157. }
  158. if (_config.cancelToken || _config.signal) {
  159. // Handle cancellation
  160. // eslint-disable-next-line func-names
  161. onCanceled = (cancel) => {
  162. if (!request) {
  163. return;
  164. }
  165. reject(!cancel || cancel.type ? new CanceledError(null, config, request) : cancel);
  166. request.abort();
  167. request = null;
  168. };
  169. _config.cancelToken && _config.cancelToken.subscribe(onCanceled);
  170. if (_config.signal) {
  171. _config.signal.aborted
  172. ? onCanceled()
  173. : _config.signal.addEventListener('abort', onCanceled);
  174. }
  175. }
  176. const protocol = parseProtocol(_config.url);
  177. if (protocol && platform.protocols.indexOf(protocol) === -1) {
  178. reject(
  179. new AxiosError(
  180. 'Unsupported protocol ' + protocol + ':',
  181. AxiosError.ERR_BAD_REQUEST,
  182. config
  183. )
  184. );
  185. return;
  186. }
  187. // Send the request
  188. request.send(requestData || null);
  189. });
  190. };