2
0
mirror of https://github.com/tenrok/axios.git synced 2026-05-15 11:59:42 +03:00
Files
axios/lib/adapters/fetch.js
T
2024-04-28 22:33:49 +03:00

198 lines
5.0 KiB
JavaScript

import platform from "../platform/index.js";
import utils from "../utils.js";
import AxiosError from "../core/AxiosError.js";
import composeSignals from "../helpers/composeSignals.js";
import {trackStream} from "../helpers/trackStream.js";
import AxiosHeaders from "../core/AxiosHeaders.js";
import progressEventReducer from "../helpers/progressEventReducer.js";
import resolveConfig from "../helpers/resolveConfig.js";
import settle from "../core/settle.js";
const fetchProgressDecorator = (total, fn) => {
const lengthComputable = total != null;
return (loaded) => setTimeout(() => fn({
lengthComputable,
total,
loaded
}));
}
const isFetchSupported = typeof fetch !== 'undefined';
const supportsRequestStreams = isFetchSupported && (() => {
let duplexAccessed = false;
const hasContentType = new Request(platform.origin, {
body: new ReadableStream(),
method: 'POST',
get duplex() {
duplexAccessed = true;
return 'half';
},
}).headers.has('Content-Type');
return duplexAccessed && !hasContentType;
})();
const DEFAULT_CHUNK_SIZE = 64 * 1024;
const resolvers = {
stream: (res) => res.body
};
isFetchSupported && ['text', 'arrayBuffer', 'blob', 'formData'].forEach(type => [
resolvers[type] = utils.isFunction(Response.prototype[type]) ? (res) => res[type]() : (_, config) => {
throw new AxiosError(`Response type ${type} is not supported`, AxiosError.ERR_NOT_SUPPORT, config);
}
])
const getBodyLength = async (body) => {
if(utils.isBlob(body)) {
return body.size;
}
if(utils.isSpecCompliantForm(body)) {
return (await new Request(body).arrayBuffer()).byteLength;
}
if(utils.isArrayBufferView(body)) {
return body.byteLength;
}
if(utils.isURLSearchParams(body)) {
body = body + '';
}
if(utils.isString(body)) {
return (await new TextEncoder().encode(body)).byteLength;
}
}
const resolveBodyLength = async (headers, body) => {
const length = utils.toFiniteNumber(headers.getContentLength());
return length == null ? getBodyLength(body) : length;
}
export default async (config) => {
let {
url,
method,
data,
signal,
cancelToken,
timeout,
onDownloadProgress,
onUploadProgress,
responseType,
headers,
withCredentials = 'same-origin',
fetchOptions
} = resolveConfig(config);
responseType = responseType ? (responseType + '').toLowerCase() : 'text';
let [composedSignal, stopTimeout] = (signal || cancelToken || timeout) ?
composeSignals([signal, cancelToken], timeout) : [];
let finished, request;
const onFinish = () => {
!finished && setTimeout(() => {
composedSignal && composedSignal.unsubscribe();
});
finished = true;
}
try {
if (onUploadProgress && supportsRequestStreams && method !== 'get' && method !== 'head') {
let requestContentLength = await resolveBodyLength(headers, data);
let _request = new Request(url, {
method,
body: data,
duplex: "half"
});
let contentTypeHeader;
if (utils.isFormData(data) && (contentTypeHeader = _request.headers.get('content-type'))) {
headers.setContentType(contentTypeHeader)
}
data = trackStream(_request.body, DEFAULT_CHUNK_SIZE, fetchProgressDecorator(
requestContentLength,
progressEventReducer(onUploadProgress)
));
}
if (!utils.isString(withCredentials)) {
withCredentials = withCredentials ? 'cors' : 'omit';
}
request = new Request(url, {
...fetchOptions,
signal: composedSignal,
method,
headers: headers.normalize().toJSON(),
body: data,
duplex: "half",
withCredentials
});
let response = await fetch(request);
const isStreamResponse = responseType === 'stream' || responseType === 'response';
if (onDownloadProgress || isStreamResponse) {
const options = {};
Object.getOwnPropertyNames(response).forEach(prop => {
options[prop] = response[prop];
});
const responseContentLength = utils.toFiniteNumber(response.headers.get('content-length'));
response = new Response(
trackStream(response.body, DEFAULT_CHUNK_SIZE, onDownloadProgress && fetchProgressDecorator(
responseContentLength,
progressEventReducer(onDownloadProgress, true)
), isStreamResponse && onFinish),
options
);
}
responseType = responseType || 'text';
let responseData = await resolvers[utils.findKey(resolvers, responseType) || 'text'](response, config);
!isStreamResponse && onFinish();
stopTimeout && stopTimeout();
return await new Promise((resolve, reject) => {
settle(resolve, reject, {
data: responseData,
headers: AxiosHeaders.from(response.headers),
status: response.status,
statusText: response.statusText,
config,
request
})
})
} catch (err) {
onFinish();
let {code} = err;
if (err.name === 'NetworkError') {
code = AxiosError.ERR_NETWORK;
}
throw AxiosError.from(err, code, config, request);
}
}