mirror of
https://github.com/tenrok/axios.git
synced 2026-06-11 18:02:32 +03:00
feat(adapter): add fetch adapter; (#6371)
This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user