UNPKG

@happy-ts/fetch-t

Version:

Abortable fetch wrapper with the ability to specify the return type.

156 lines (150 loc) 4.24 kB
'use strict'; var happyRusty = require('happy-rusty'); var invariant = require('tiny-invariant'); const ABORT_ERROR = "AbortError"; const TIMEOUT_ERROR = "TimeoutError"; class FetchError extends Error { /** * The name of the error. */ name = "FetchError"; /** * The status code of the response. */ status = 0; constructor(message, status) { super(message); this.status = status; } } function fetchT(url, init) { if (typeof url !== "string") { invariant(url instanceof URL, () => `Url must be a string or URL object but received ${url}.`); } const { // default not abort able abortable = false, responseType, timeout, onProgress, onChunk, ...rest } = init ?? {}; const shouldWaitTimeout = timeout != null; let cancelTimer; if (shouldWaitTimeout) { invariant(typeof timeout === "number" && timeout > 0, () => `Timeout must be a number greater than 0 but received ${timeout}.`); } let controller; if (abortable || shouldWaitTimeout) { controller = new AbortController(); rest.signal = controller.signal; } const response = fetch(url, rest).then(async (res) => { cancelTimer?.(); if (!res.ok) { await res.body?.cancel(); return happyRusty.Err(new FetchError(res.statusText, res.status)); } if (res.body) { const shouldNotifyProgress = typeof onProgress === "function"; const shouldNotifyChunk = typeof onChunk === "function"; if (shouldNotifyProgress || shouldNotifyChunk) { const [stream1, stream2] = res.body.tee(); const reader = stream1.getReader(); let totalByteLength = null; let completedByteLength = 0; if (shouldNotifyProgress) { const contentLength = res.headers.get("content-length") ?? res.headers.get("Content-Length"); if (contentLength == null) { onProgress(happyRusty.Err(new Error("No content-length in response headers."))); } else { totalByteLength = parseInt(contentLength, 10); } } reader.read().then(function notify({ done, value }) { if (done) { return; } if (shouldNotifyChunk) { onChunk(value); } if (shouldNotifyProgress && totalByteLength != null) { completedByteLength += value.byteLength; onProgress(happyRusty.Ok({ totalByteLength, completedByteLength })); } reader.read().then(notify); }); res = new Response(stream2, { headers: res.headers, status: res.status, statusText: res.statusText }); } } switch (responseType) { case "arraybuffer": { return happyRusty.Ok(await res.arrayBuffer()); } case "blob": { return happyRusty.Ok(await res.blob()); } case "json": { try { return happyRusty.Ok(await res.json()); } catch { return happyRusty.Err(new Error("Response is invalid json while responseType is json")); } } case "text": { return happyRusty.Ok(await res.text()); } default: { return happyRusty.Ok(res); } } }).catch((err) => { cancelTimer?.(); return happyRusty.Err(err); }); if (shouldWaitTimeout) { const timer = setTimeout(() => { if (!controller.signal.aborted) { const error = new Error(); error.name = TIMEOUT_ERROR; controller.abort(error); } }, timeout); cancelTimer = () => { if (timer) { clearTimeout(timer); } cancelTimer = null; }; } if (abortable) { return { // eslint-disable-next-line @typescript-eslint/no-explicit-any abort(reason) { cancelTimer?.(); controller.abort(reason); }, get aborted() { return controller.signal.aborted; }, get response() { return response; } }; } else { return response; } } exports.ABORT_ERROR = ABORT_ERROR; exports.FetchError = FetchError; exports.TIMEOUT_ERROR = TIMEOUT_ERROR; exports.fetchT = fetchT; //# sourceMappingURL=main.cjs.map