UNPKG

stream-to-it

Version:

Convert Node.js streams to streaming iterables

112 lines 3.79 kB
/** * Convert a Node.js [`Writable`](https://nodejs.org/dist/latest/docs/api/stream.html#class-streamwritable) * stream to a [sink](https://achingbrain.github.io/it-stream-types/interfaces/Sink.html). */ export function sink(writable) { return async (source) => { const maybeEndSource = async () => { if (isAsyncGenerator(source)) { await source.return(undefined); } }; let error; let errCb; const errorHandler = (err) => { error = err; // When the writable errors, try to end the source to exit iteration early maybeEndSource() .catch(err => { err = new AggregateError([ error, err ], 'The Writable emitted an error, additionally an error occurred while ending the Source'); }) .finally(() => { errCb?.(err); }); }; let closeCb; let closed = false; const closeHandler = () => { closed = true; closeCb?.(); }; let finishCb; let finished = false; const finishHandler = () => { finished = true; finishCb?.(); }; let drainCb; const drainHandler = () => { drainCb?.(); }; const waitForDrainOrClose = async () => { return new Promise((resolve, reject) => { closeCb = drainCb = resolve; errCb = reject; writable.once('drain', drainHandler); }); }; const waitForDone = async () => { // Immediately try to end the source await maybeEndSource(); return new Promise((resolve, reject) => { if (closed || finished || (error != null)) { resolve(); return; } finishCb = closeCb = resolve; errCb = reject; }); }; const cleanup = () => { writable.removeListener('error', errorHandler); writable.removeListener('close', closeHandler); writable.removeListener('finish', finishHandler); writable.removeListener('drain', drainHandler); }; writable.once('error', errorHandler); writable.once('close', closeHandler); writable.once('finish', finishHandler); try { for await (const value of source) { if (!writable.writable || writable.destroyed || (error != null)) { break; } if (!writable.write(value)) { await waitForDrainOrClose(); } } } catch (err) { // error is set by stream error handler so only destroy stream if source // threw if (error == null) { writable.destroy(err); } // could we be obscuring an error here? error = err; } try { // We're done writing, end everything (n.b. stream may be destroyed at this // point but then this is a no-op) if (writable.writable) { writable.end(); } // Wait until we close or finish. This supports halfClosed streams await waitForDone(); // Notify the user an error occurred if (error != null) throw error; } finally { // Clean up listeners cleanup(); } }; } function isAsyncGenerator(obj) { return obj.return != null; } //# sourceMappingURL=sink.js.map