UNPKG

@v4fire/core

Version:
183 lines (141 loc) 4.02 kB
/*! * 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;