xhr.js 7.2 KB

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