UNPKG

@naturalcycles/nodejs-lib

Version:
92 lines (91 loc) 3.79 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.transformMap = void 0; const js_lib_1 = require("@naturalcycles/js-lib"); const through2Concurrent = require("through2-concurrent"); const colors_1 = require("../../colors"); const stream_util_1 = require("../stream.util"); // doesn't work, cause here we don't construct our Transform instance ourselves // export class TransformMap extends AbortableTransform {} /** * Like pMap, but for streams. * Inspired by `through2`. * Main feature is concurrency control (implemented via `through2-concurrent`) and convenient options. * Using this allows native stream .pipe() to work and use backpressure. * * Only works in objectMode (due to through2Concurrent). * * Concurrency defaults to 16. * * If an Array is returned by `mapper` - it will be flattened and multiple results will be emitted from it. Tested by Array.isArray(). */ function transformMap(mapper, opt = {}) { const { concurrency = 16, predicate, // we now default to "no predicate" (meaning pass-everything) errorMode = js_lib_1.ErrorMode.THROW_IMMEDIATELY, flattenArrayOutput, onError, metric = 'stream', logger = console, } = opt; let index = -1; let isSettled = false; let errors = 0; const collectedErrors = []; // only used if errorMode == THROW_AGGREGATED return through2Concurrent.obj({ maxConcurrency: concurrency, async final(cb) { // console.log('transformMap final') logErrorStats(true); if (collectedErrors.length) { // emit Aggregated error cb(new js_lib_1.AggregatedError(collectedErrors)); } else { // emit no error cb(); } }, }, async function transformMapFn(chunk, _, cb) { // Stop processing if isSettled (either THROW_IMMEDIATELY was fired or END received) if (isSettled) return cb(); const currentIndex = ++index; try { const res = await mapper(chunk, currentIndex); const passedResults = await (0, js_lib_1.pFilter)(flattenArrayOutput && Array.isArray(res) ? res : [res], async (r) => { if (r === js_lib_1.END) { isSettled = true; // will be checked later return false; } return r !== js_lib_1.SKIP && (!predicate || (await predicate(r, currentIndex))); }); passedResults.forEach(r => this.push(r)); if (isSettled) { logger.log(`transformMap END received at index ${currentIndex}`); (0, stream_util_1.pipelineClose)('transformMap', this, this.sourceReadable, this.streamDone, logger); } cb(); // done processing } catch (err) { logger.error(err); errors++; logErrorStats(); if (onError) { try { onError((0, js_lib_1._anyToError)(err), chunk); } catch { } } if (errorMode === js_lib_1.ErrorMode.THROW_IMMEDIATELY) { isSettled = true; return cb(err); // Emit error immediately } if (errorMode === js_lib_1.ErrorMode.THROW_AGGREGATED) { collectedErrors.push(err); } // Tell input stream that we're done processing, but emit nothing to output - not error nor result cb(); } }); function logErrorStats(final = false) { if (!errors) return; logger.log(`${metric} ${final ? 'final ' : ''}errors: ${(0, colors_1.yellow)(errors)}`); } } exports.transformMap = transformMap;