xhr.js 6.7 KB

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