xhr.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  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() {
  91. // Real errors are hidden from us by the browser
  92. // onerror should only fire if it's a network error
  93. reject(new AxiosError('Network Error', AxiosError.ERR_NETWORK, config, request));
  94. // Clean up request
  95. request = null;
  96. };
  97. // Handle timeout
  98. request.ontimeout = function handleTimeout() {
  99. let timeoutErrorMessage = _config.timeout ? 'timeout of ' + _config.timeout + 'ms exceeded' : 'timeout exceeded';
  100. const transitional = _config.transitional || transitionalDefaults;
  101. if (_config.timeoutErrorMessage) {
  102. timeoutErrorMessage = _config.timeoutErrorMessage;
  103. }
  104. reject(new AxiosError(
  105. timeoutErrorMessage,
  106. transitional.clarifyTimeoutError ? AxiosError.ETIMEDOUT : AxiosError.ECONNABORTED,
  107. config,
  108. request));
  109. // Clean up request
  110. request = null;
  111. };
  112. // Remove Content-Type if data is undefined
  113. requestData === undefined && requestHeaders.setContentType(null);
  114. // Add headers to the request
  115. if ('setRequestHeader' in request) {
  116. utils.forEach(requestHeaders.toJSON(), function setRequestHeader(val, key) {
  117. request.setRequestHeader(key, val);
  118. });
  119. }
  120. // Add withCredentials to request if needed
  121. if (!utils.isUndefined(_config.withCredentials)) {
  122. request.withCredentials = !!_config.withCredentials;
  123. }
  124. // Add responseType to request if needed
  125. if (responseType && responseType !== 'json') {
  126. request.responseType = _config.responseType;
  127. }
  128. // Handle progress if needed
  129. if (onDownloadProgress) {
  130. ([downloadThrottled, flushDownload] = progressEventReducer(onDownloadProgress, true));
  131. request.addEventListener('progress', downloadThrottled);
  132. }
  133. // Not all browsers support upload events
  134. if (onUploadProgress && request.upload) {
  135. ([uploadThrottled, flushUpload] = progressEventReducer(onUploadProgress));
  136. request.upload.addEventListener('progress', uploadThrottled);
  137. request.upload.addEventListener('loadend', flushUpload);
  138. }
  139. if (_config.cancelToken || _config.signal) {
  140. // Handle cancellation
  141. // eslint-disable-next-line func-names
  142. onCanceled = cancel => {
  143. if (!request) {
  144. return;
  145. }
  146. reject(!cancel || cancel.type ? new CanceledError(null, config, request) : cancel);
  147. request.abort();
  148. request = null;
  149. };
  150. _config.cancelToken && _config.cancelToken.subscribe(onCanceled);
  151. if (_config.signal) {
  152. _config.signal.aborted ? onCanceled() : _config.signal.addEventListener('abort', onCanceled);
  153. }
  154. }
  155. const protocol = parseProtocol(_config.url);
  156. if (protocol && platform.protocols.indexOf(protocol) === -1) {
  157. reject(new AxiosError('Unsupported protocol ' + protocol + ':', AxiosError.ERR_BAD_REQUEST, config));
  158. return;
  159. }
  160. // Send the request
  161. request.send(requestData || null);
  162. });
  163. }