xhr.js 8.2 KB

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