UNPKG

nano-spawn

Version:

Tiny process execution for humans — a better child_process

68 lines (58 loc) 1.95 kB
import * as readline from 'node:readline/promises'; export const lineIterator = async function * (subprocess, {state}, streamName) { // Prevent buffering when iterating. // This would defeat one of the main goals of iterating: low memory consumption. if (state.isIterating === false) { throw new Error(`The subprocess must be iterated right away, for example: for await (const line of spawn(...)) { ... }`); } state.isIterating = true; try { const {[streamName]: stream} = await subprocess.nodeChildProcess; if (!stream) { return; } handleErrors(subprocess); yield * readline.createInterface({input: stream}); } finally { await subprocess; } }; // When the `subprocess` promise is rejected, we await it in the `finally` // block. However, this might not happen right away, so an `unhandledRejection` // error is emitted first, crashing the process. This prevents it. // This is safe since we are guaranteed to propagate the `subprocess` error // with the `finally` block. // See https://github.com/sindresorhus/nano-spawn/issues/104 const handleErrors = async subprocess => { try { await subprocess; } catch {} }; // Merge two async iterators into one export const combineAsyncIterators = async function * (...iterators) { try { let promises = []; while (iterators.length > 0) { promises = iterators.map((iterator, index) => promises[index] ?? getNext(iterator)); // eslint-disable-next-line no-await-in-loop const [{value, done}, index] = await Promise.race(promises .map((promise, index) => Promise.all([promise, index]))); const [iterator] = iterators.splice(index, 1); promises.splice(index, 1); if (!done) { iterators.push(iterator); yield value; } } } finally { await Promise.all(iterators.map(iterator => iterator.return())); } }; const getNext = async iterator => { try { return await iterator.next(); } catch (error) { await iterator.throw(error); } };