UNPKG

@ayonli/jsext

Version:

A JavaScript extension package for building strong and modern applications.

337 lines (334 loc) 13.6 kB
import chan from '../chan.js'; import { fromErrorEvent } from '../error.js'; function isFunction(val) { return typeof val === "function"; } async function* resolveAsyncIterable(promise) { const stream = await promise; const reader = stream.getReader(); try { while (true) { const { done, value } = await reader.read(); if (done) { break; } yield value; } } finally { reader.releaseLock(); } } function resolveReadableStream(promise) { const { readable, writable } = new TransformStream(); promise.then(stream => stream.pipeTo(writable)); return readable; } /** * If the given `promise` resolves to a `ReadableStream<Uint8Array>`, this * function will return a new `ReadableStream<Uint8Array>` object that can be * used to read the byte stream without the need to wait for the promise to * resolve. * * This function is optimized for zero-copy read, so it's recommended to use * this function when the source stream is a byte stream. */ function resolveByteStream(promise) { let reader; return new ReadableStream({ type: "bytes", async start() { const source = await promise; try { // zero-copy read from the source stream reader = source.getReader({ mode: "byob" }); } catch (_a) { reader = source.getReader(); } }, async pull(controller) { var _a; try { let request; let view; let result; if ("byobRequest" in controller && ((_a = controller.byobRequest) === null || _a === void 0 ? void 0 : _a.view)) { // This stream is requested for zero-copy read. request = controller.byobRequest; view = request.view; } else if (reader instanceof ReadableStreamBYOBReader) { view = new Uint8Array(4096); } if (reader instanceof ReadableStreamBYOBReader) { // The source stream supports zero-copy read, we can read its // data directly into the request view's buffer. result = await reader.read(view); } else { // The source stream does not support zero-copy read, we need to // copy its data to a new buffer. result = await reader.read(); } if (request) { if (result.done) { controller.close(); // The final chunk may be empty, but still needs to be // responded in order to close the request reader. if (result.value !== undefined) { request.respondWithNewView(result.value); } else { request.respond(0); } } else if (reader instanceof ReadableStreamBYOBReader || (view && result.value.buffer.byteLength === view.buffer.byteLength)) { // Respond to the request reader with the same underlying // buffer of the source stream. // Or the source stream doesn't support zero-copy read, but // the result bytes has the same buffer size as the request // view. request.respondWithNewView(result.value); } else { // This stream is requested for zero-copy read, but the // source stream doesn't support it. We need to copy and // deliver the new buffer instead. controller.enqueue(result.value); } } else { if (result.done) { controller.close(); } else { controller.enqueue(result.value); } } } catch (err) { reader.releaseLock(); controller.error(err); } }, cancel(reason = undefined) { reader.cancel(reason); reader.releaseLock(); }, }); } /** * Converts the given `source` into an `AsyncIterable` object if it's not one * already, returns `null` if failed. */ function asAsyncIterable(source) { if (typeof source[Symbol.asyncIterator] === "function") { return source; } else if (typeof source[Symbol.iterator] === "function") { return { [Symbol.asyncIterator]: async function* () { for (const value of source) { yield value; } }, }; } else if (typeof ReadableStream === "function" && source instanceof ReadableStream) { const reader = source.getReader(); return { [Symbol.asyncIterator]: async function* () { try { while (true) { const { done, value } = await reader.read(); if (done) { break; } yield value; } } catch (err) { reader.cancel(err); } finally { reader.releaseLock(); } }, }; } else if (typeof source["then"] === "function") { return resolveAsyncIterable(source); } return null; } function toAsyncIterable(source, eventMap = undefined) { var _a; const iterable = asAsyncIterable(source); if (iterable) { return iterable; } const channel = chan(Infinity); const handleMessage = channel.send.bind(channel); const handleClose = channel.close.bind(channel); const handleBrowserErrorEvent = (ev) => { let err; if (typeof ErrorEvent === "function" && ev instanceof ErrorEvent) { err = ev.error || fromErrorEvent(ev); } else { // @ts-ignore err = new Error("something went wrong", { cause: ev }); } handleClose(err); }; const proto = Object.getPrototypeOf(source); const msgDesc = Object.getOwnPropertyDescriptor(proto, "onmessage"); if ((msgDesc === null || msgDesc === void 0 ? void 0 : msgDesc.set) && isFunction(source["close"])) { // WebSocket or EventSource const errDesc = Object.getOwnPropertyDescriptor(proto, "onerror"); const closeDesc = Object.getOwnPropertyDescriptor(proto, "onclose"); let cleanup; if ((eventMap === null || eventMap === void 0 ? void 0 : eventMap.event) && (eventMap === null || eventMap === void 0 ? void 0 : eventMap.event) !== "message" && isFunction(source["addEventListener"])) { // for EventSource listening on custom events const es = source; const eventName = eventMap.event; const msgListener = (ev) => { handleMessage(ev.data); }; es.addEventListener(eventName, msgListener); cleanup = () => { es.removeEventListener(eventName, msgListener); }; } else { msgDesc.set.call(source, (ev) => { handleMessage(ev.data); }); cleanup = () => { var _a; (_a = msgDesc.set) === null || _a === void 0 ? void 0 : _a.call(source, null); }; } (_a = errDesc === null || errDesc === void 0 ? void 0 : errDesc.set) === null || _a === void 0 ? void 0 : _a.call(source, handleBrowserErrorEvent); if (closeDesc === null || closeDesc === void 0 ? void 0 : closeDesc.set) { // WebSocket closeDesc.set.call(source, () => { var _a, _b; handleClose(); (_a = closeDesc.set) === null || _a === void 0 ? void 0 : _a.call(source, null); (_b = errDesc === null || errDesc === void 0 ? void 0 : errDesc.set) === null || _b === void 0 ? void 0 : _b.call(source, null); cleanup === null || cleanup === void 0 ? void 0 : cleanup(); }); } else if (!(closeDesc === null || closeDesc === void 0 ? void 0 : closeDesc.set) && isFunction(source["close"])) { // EventSource // EventSource by default does not trigger close event, we need to // make sure when it calls the close() function, the iterator is // automatically closed. const es = source; const _close = es.close; Object.defineProperty(es, "close", { configurable: true, writable: true, value: function close() { var _a; _close.call(es); handleClose(); Object.defineProperty(es, "close", { configurable: true, writable: true, value: _close, }); (_a = errDesc === null || errDesc === void 0 ? void 0 : errDesc.set) === null || _a === void 0 ? void 0 : _a.call(source, null); cleanup === null || cleanup === void 0 ? void 0 : cleanup(); } }); } } else if (isFunction(source["send"]) && isFunction(source["close"])) { // non-standard WebSocket implementation or WebSocket-like object const ws = source; if (typeof ws.addEventListener === "function") { const msgListener = (ev) => { handleMessage(ev.data); }; ws.addEventListener("message", msgListener); ws.addEventListener("error", handleBrowserErrorEvent); ws.addEventListener("close", () => { handleClose(); ws.removeEventListener("message", msgListener); ws.removeEventListener("error", handleBrowserErrorEvent); }); } else { ws.onmessage = (ev) => { handleMessage(ev.data); }; ws.onerror = handleBrowserErrorEvent; ws.onclose = () => { handleClose(); ws.onclose = null; ws.onerror = null; ws.onmessage = null; }; } } else if (isFunction(source["addEventListener"])) { // EventTarget const target = source; const msgEvent = (eventMap === null || eventMap === void 0 ? void 0 : eventMap.message) || "message"; const errEvent = (eventMap === null || eventMap === void 0 ? void 0 : eventMap.error) || "error"; const closeEvent = (eventMap === null || eventMap === void 0 ? void 0 : eventMap.close) || "close"; const msgListener = (ev) => { if (ev instanceof MessageEvent) { handleMessage(ev.data); } }; target.addEventListener(msgEvent, msgListener); target.addEventListener(errEvent, handleBrowserErrorEvent); target.addEventListener(closeEvent, function closeListener() { handleClose(); target.removeEventListener(closeEvent, closeListener); target.removeEventListener(msgEvent, msgListener); target.removeEventListener(errEvent, handleBrowserErrorEvent); }); } else if (isFunction(source["on"])) { // EventEmitter const target = source; let dataEvent; let errEvent; let closeEvent; if (typeof process === "object" && source === process) { dataEvent = "message"; errEvent = "uncaughtException"; closeEvent = "exit"; } else if ((isFunction(source["send"]) && isFunction(source["kill"])) || // child process (isFunction(source["postMessage"]) && isFunction(source["terminate"])) || // worker thread (isFunction(source["postMessage"]) && isFunction(source["close"])) // message port ) { dataEvent = "message"; errEvent = "error"; closeEvent = "exit"; } else { dataEvent = (eventMap === null || eventMap === void 0 ? void 0 : eventMap.data) || "data"; errEvent = (eventMap === null || eventMap === void 0 ? void 0 : eventMap.error) || "error"; closeEvent = (eventMap === null || eventMap === void 0 ? void 0 : eventMap.close) || "close"; } target.on(dataEvent, handleMessage); target.once(errEvent, handleClose); target.once(closeEvent, () => { handleClose(); target.off(dataEvent, handleMessage); target.off(errEvent, handleClose); }); } else { throw new TypeError("The source cannot be converted to an async iterable object."); } return { [Symbol.asyncIterator]: channel[Symbol.asyncIterator].bind(channel), }; } export { asAsyncIterable, resolveByteStream, resolveReadableStream, toAsyncIterable }; //# sourceMappingURL=util.js.map