xhr.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. 'use strict';
  2. import utils from './../utils.js';
  3. import settle from './../core/settle.js';
  4. import cookies from './../helpers/cookies.js';
  5. import buildURL from './../helpers/buildURL.js';
  6. import buildFullPath from '../core/buildFullPath.js';
  7. import isURLSameOrigin from './../helpers/isURLSameOrigin.js';
  8. import transitionalDefaults from '../defaults/transitional.js';
  9. import AxiosError from '../core/AxiosError.js';
  10. import CanceledError from '../cancel/CanceledError.js';
  11. import parseProtocol from '../helpers/parseProtocol.js';
  12. import platform from '../platform/index.js';
  13. import AxiosHeaders from '../core/AxiosHeaders.js';
  14. import speedometer from '../helpers/speedometer.js';
  15. function progressEventReducer(listener, isDownloadStream) {
  16. let bytesNotified = 0;
  17. const _speedometer = speedometer(50, 250);
  18. return e => {
  19. const loaded = e.loaded;
  20. const total = e.lengthComputable ? e.total : undefined;
  21. const progressBytes = loaded - bytesNotified;
  22. const rate = _speedometer(progressBytes);
  23. const inRange = loaded <= total;
  24. bytesNotified = loaded;
  25. const data = {
  26. loaded,
  27. total,
  28. progress: total ? (loaded / total) : undefined,
  29. bytes: progressBytes,
  30. rate: rate ? rate : undefined,
  31. estimated: rate && total && inRange ? (total - loaded) / rate : undefined,
  32. event: e
  33. };
  34. data[isDownloadStream ? 'download' : 'upload'] = true;
  35. listener(data);
  36. };
  37. }
  38. const isXHRAdapterSupported = typeof XMLHttpRequest !== 'undefined';
  39. export default isXHRAdapterSupported && function (config) {
  40. return new Promise(function dispatchXhrRequest(resolve, reject) {
  41. let requestData = config.data;
  42. const requestHeaders = AxiosHeaders.from(config.headers).normalize();
  43. const responseType = config.responseType;
  44. let onCanceled;
  45. function done() {
  46. if (config.cancelToken) {
  47. config.cancelToken.unsubscribe(onCanceled);
  48. }
  49. if (config.signal) {
  50. config.signal.removeEventListener('abort', onCanceled);
  51. }
  52. }
  53. if (utils.isFormData(requestData) && (platform.isStandardBrowserEnv || platform.isStandardBrowserWebWorkerEnv)) {
  54. requestHeaders.setContentType(false); // Let the browser set it
  55. }
  56. let request = new XMLHttpRequest();
  57. // HTTP basic authentication
  58. if (config.auth) {
  59. const username = config.auth.username || '';
  60. const password = config.auth.password ? unescape(encodeURIComponent(config.auth.password)) : '';
  61. requestHeaders.set('Authorization', 'Basic ' + btoa(username + ':' + password));
  62. }
  63. const fullPath = buildFullPath(config.baseURL, config.url);
  64. request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true);
  65. // Set the request timeout in MS
  66. request.timeout = config.timeout;
  67. function onloadend() {
  68. if (!request) {
  69. return;
  70. }
  71. // Prepare the response
  72. const responseHeaders = AxiosHeaders.from(
  73. 'getAllResponseHeaders' in request && request.getAllResponseHeaders()
  74. );
  75. const responseData = !responseType || responseType === 'text' || responseType === 'json' ?
  76. request.responseText : request.response;
  77. const response = {
  78. data: responseData,
  79. status: request.status,
  80. statusText: request.statusText,
  81. headers: responseHeaders,
  82. config,
  83. request
  84. };
  85. settle(function _resolve(value) {
  86. resolve(value);
  87. done();
  88. }, function _reject(err) {
  89. reject(err);
  90. done();
  91. }, response);
  92. // Clean up request
  93. request = null;
  94. }
  95. if ('onloadend' in request) {
  96. // Use onloadend if available
  97. request.onloadend = onloadend;
  98. } else {
  99. // Listen for ready state to emulate onloadend
  100. request.onreadystatechange = function handleLoad() {
  101. if (!request || request.readyState !== 4) {
  102. return;
  103. }
  104. // The request errored out and we didn't get a response, this will be
  105. // handled by onerror instead
  106. // With one exception: request that using file: protocol, most browsers
  107. // will return status as 0 even though it's a successful request
  108. if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) {
  109. return;
  110. }
  111. // readystate handler is calling before onerror or ontimeout handlers,
  112. // so we should call onloadend on the next 'tick'
  113. setTimeout(onloadend);
  114. };
  115. }
  116. // Handle browser request cancellation (as opposed to a manual cancellation)
  117. request.onabort = function handleAbort() {
  118. if (!request) {
  119. return;
  120. }
  121. reject(new AxiosError('Request aborted', AxiosError.ECONNABORTED, config, request));
  122. // Clean up request
  123. request = null;
  124. };
  125. // Handle low level network errors
  126. request.onerror = function handleError() {
  127. // Real errors are hidden from us by the browser
  128. // onerror should only fire if it's a network error
  129. reject(new AxiosError('Network Error', AxiosError.ERR_NETWORK, config, request));
  130. // Clean up request
  131. request = null;
  132. };
  133. // Handle timeout
  134. request.ontimeout = function handleTimeout() {
  135. let timeoutErrorMessage = config.timeout ? 'timeout of ' + config.timeout + 'ms exceeded' : 'timeout exceeded';
  136. const transitional = config.transitional || transitionalDefaults;
  137. if (config.timeoutErrorMessage) {
  138. timeoutErrorMessage = config.timeoutErrorMessage;
  139. }
  140. reject(new AxiosError(
  141. timeoutErrorMessage,
  142. transitional.clarifyTimeoutError ? AxiosError.ETIMEDOUT : AxiosError.ECONNABORTED,
  143. config,
  144. request));
  145. // Clean up request
  146. request = null;
  147. };
  148. // Add xsrf header
  149. // This is only done if running in a standard browser environment.
  150. // Specifically not if we're in a web worker, or react-native.
  151. if (platform.isStandardBrowserEnv) {
  152. // Add xsrf header
  153. const xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath))
  154. && config.xsrfCookieName && cookies.read(config.xsrfCookieName);
  155. if (xsrfValue) {
  156. requestHeaders.set(config.xsrfHeaderName, xsrfValue);
  157. }
  158. }
  159. // Remove Content-Type if data is undefined
  160. requestData === undefined && requestHeaders.setContentType(null);
  161. // Add headers to the request
  162. if ('setRequestHeader' in request) {
  163. utils.forEach(requestHeaders.toJSON(), function setRequestHeader(val, key) {
  164. request.setRequestHeader(key, val);
  165. });
  166. }
  167. // Add withCredentials to request if needed
  168. if (!utils.isUndefined(config.withCredentials)) {
  169. request.withCredentials = !!config.withCredentials;
  170. }
  171. // Add responseType to request if needed
  172. if (responseType && responseType !== 'json') {
  173. request.responseType = config.responseType;
  174. }
  175. // Handle progress if needed
  176. if (typeof config.onDownloadProgress === 'function') {
  177. request.addEventListener('progress', progressEventReducer(config.onDownloadProgress, true));
  178. }
  179. // Not all browsers support upload events
  180. if (typeof config.onUploadProgress === 'function' && request.upload) {
  181. request.upload.addEventListener('progress', progressEventReducer(config.onUploadProgress));
  182. }
  183. if (config.cancelToken || config.signal) {
  184. // Handle cancellation
  185. // eslint-disable-next-line func-names
  186. onCanceled = cancel => {
  187. if (!request) {
  188. return;
  189. }
  190. reject(!cancel || cancel.type ? new CanceledError(null, config, request) : cancel);
  191. request.abort();
  192. request = null;
  193. };
  194. config.cancelToken && config.cancelToken.subscribe(onCanceled);
  195. if (config.signal) {
  196. config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled);
  197. }
  198. }
  199. const protocol = parseProtocol(fullPath);
  200. if (protocol && platform.protocols.indexOf(protocol) === -1) {
  201. reject(new AxiosError('Unsupported protocol ' + protocol + ':', AxiosError.ERR_BAD_REQUEST, config));
  202. return;
  203. }
  204. // Send the request
  205. request.send(requestData || null);
  206. });
  207. }