Search / static /reader-request-manager.js
OpenCode
perf: prioritize visible OCR text requests
4f2a8c7
Raw History Blame Contribute Delete
4.64 kB
(function (root) {
"use strict";
function createReaderRequestManager(options = {}) {
const fetchImpl = options.fetchImpl || root.fetch.bind(root);
const AbortControllerImpl = options.AbortControllerImpl || root.AbortController;
const setTimer = options.setTimer || root.setTimeout.bind(root);
const clearTimer = options.clearTimer || root.clearTimeout.bind(root);
const pending = new Map();
const active = new Set();
let disposed = false;
function settle(record) {
if (record.settled) return;
record.settled = true;
clearTimer(record.timeout);
active.delete(record);
}
function abort(record, reason) {
if (record.settled) return;
record.controller.abort(reason);
record.abortBody?.(reason);
settle(record);
}
function responseForCaller(record) {
return record.promise.then((response) => {
if (!record.responseClaimed) {
record.responseClaimed = true;
return response;
}
return typeof response?.clone === "function" ? response.clone() : response;
});
}
function wrapResponse(record, response) {
const ResponseImpl = root.Response || (typeof Response === "function" ? Response : null);
const ReadableStreamImpl =
root.ReadableStream || (typeof ReadableStream === "function" ? ReadableStream : null);
if (!response?.body?.getReader || !ResponseImpl || !ReadableStreamImpl) {
settle(record);
return response;
}
const sourceReader = response.body.getReader();
const stream = new ReadableStreamImpl({
start(controller) {
record.abortBody = (reason) => {
controller.error(reason);
sourceReader.cancel(reason).catch(() => {});
};
},
async pull(controller) {
try {
const result = await sourceReader.read();
if (record.settled) return;
if (result.done) {
settle(record);
controller.close();
} else {
controller.enqueue(result.value);
}
} catch (error) {
if (record.settled) return;
settle(record);
controller.error(error);
}
},
cancel(reason) {
settle(record);
return sourceReader.cancel(reason);
}
});
return new ResponseImpl(stream, {
status: response.status,
statusText: response.statusText,
headers: response.headers
});
}
function request(url, timeoutMs, requestInit = {}) {
if (disposed) return Promise.reject(new DOMException("Reader disposed", "AbortError"));
if (pending.has(url)) return responseForCaller(pending.get(url));
const controller = new AbortControllerImpl();
const record = {
controller,
timeout: null,
promise: null,
responseClaimed: false,
settled: false,
abortBody: null
};
record.timeout = setTimer(
() => abort(record, new DOMException("Reader request timed out", "TimeoutError")),
timeoutMs
);
active.add(record);
record.promise = Promise.resolve()
.then(() => {
controller.signal.throwIfAborted();
return fetchImpl(url, { ...requestInit, signal: controller.signal });
})
.then(async (response) => {
if (controller.signal.aborted) {
const reason = controller.signal.reason;
try {
await response.body?.cancel?.(reason);
} catch (_) {}
throw reason;
}
return wrapResponse(record, response);
})
.catch((error) => {
settle(record);
controller.signal.throwIfAborted();
throw error;
})
.finally(() => {
if (pending.get(url) === record) pending.delete(url);
});
pending.set(url, record);
return responseForCaller(record);
}
function dispose() {
if (disposed) return;
disposed = true;
for (const record of active) abort(record, new DOMException("Reader disposed", "AbortError"));
pending.clear();
}
return Object.freeze({
request,
dispose,
get pendingCount() {
return pending.size;
},
get activeCount() {
return active.size;
},
get disposed() {
return disposed;
}
});
}
root.VoiceOfMLReaderRequests = Object.freeze({ createReaderRequestManager });
})(typeof self !== "undefined" ? self : globalThis);