@v4fire/core
Version:
V4Fire core library
183 lines (141 loc) • 4.02 kB
text/typescript
/*!
* V4Fire Core
* https://github.com/V4Fire/Core
*
* Released under the MIT license
* https://github.com/V4Fire/Core/blob/master/LICENSE
*/
/**
* [[include:core/request/engines/fetch/README.md]]
* @packageDocumentation
*/
//#if node_js
import fetch from 'core/request/engines/fetch/node';
//#endif
import AbortablePromise from 'core/promise/abortable';
import { isOnline } from 'core/net';
import Response from 'core/request/response';
import RequestError from 'core/request/error';
import StreamBuffer from 'core/request/modules/stream-buffer';
import { convertDataToSend } from 'core/request/engines/helpers';
import type { RequestEngine, RequestResponseChunk } from 'core/request/interface';
/**
* Creates request by using the fetch API with the specified parameters and returns a promise
* @param params
*/
const request: RequestEngine = (params) => {
const
p = params,
abortController = new AbortController(),
streamBuffer = new StreamBuffer<RequestResponseChunk>();
const
[body, contentType] = convertDataToSend(p.body, p.contentType);
const
headers: Record<string, string> = {};
if (p.headers != null) {
for (const [name, val] of p.headers) {
headers[name] = val;
}
}
if (contentType != null) {
headers['Content-Type'] = contentType;
}
let
credentials: RequestCredentials = 'same-origin';
if (Object.isString(p.credentials)) {
credentials = p.credentials;
} else if (p.credentials) {
credentials = 'include';
}
const fetchOpts: RequestInit = {
body,
headers,
credentials,
method: p.method,
signal: abortController.signal
};
return new AbortablePromise<Response>(async (resolve, reject, onAbort) => {
const
{status} = await AbortablePromise.resolve(isOnline(), p.parent);
if (!status) {
return reject(new RequestError(RequestError.Offline));
}
const
req = fetch(p.url, fetchOpts);
let
timer;
if (p.timeout != null) {
timer = setTimeout(() => abortController.abort(), p.timeout);
}
onAbort((reason) => {
streamBuffer.destroy(reason);
abortController.abort();
});
req.then((res) => {
clearTimeout(timer);
const getResponse = () => {
switch (p.responseType) {
case 'json':
case 'document':
case 'text':
return res.text();
case 'formData':
return res.formData();
default:
return res.arrayBuffer();
}
};
getResponse[Symbol.asyncIterator] = () => {
const
contentLength = res.headers.get('Content-Length'),
total = contentLength != null ? Number(contentLength) : undefined;
let
loaded = 0;
if (res.body != null) {
const
reader: ReadableStreamDefaultReader<Uint8Array> = res.body.getReader();
(async () => {
// eslint-disable-next-line no-constant-condition
while (true) {
const
{done, value: data} = await reader.read();
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (done || data == null) {
streamBuffer.close();
break;
}
loaded += data.length;
streamBuffer.add({total, loaded, data});
}
})();
}
return streamBuffer[Symbol.asyncIterator]();
};
resolve(new Response(getResponse, {
url: res.url,
redirected: res.redirected,
type: res.type,
parent: p.parent,
important: p.important,
okStatuses: p.okStatuses,
noContentStatuses: p.noContentStatuses,
status: res.status,
statusText: res.statusText,
headers: res.headers,
responseType: p.responseType,
forceResponseType: p.forceResponseType,
decoder: p.decoders,
streamDecoder: p.streamDecoders,
jsonReviver: p.jsonReviver
}));
}, (error) => {
clearTimeout(timer);
const
type = error.name === 'AbortError' ? RequestError.Timeout : RequestError.Engine,
requestError = new RequestError(type, {error});
streamBuffer.destroy(requestError);
reject(requestError);
});
}, p.parent);
};
export default request;