UNPKG

for-emit-of

Version:

Turn Node.js Events into Async Iterables

200 lines (199 loc) 7.64 kB
"use strict"; const events_1 = require("events"); const sleep_1 = require("./sleep"); const timeout_1 = require("./timeout"); const debugging_1 = require("./debugging"); const instant_1 = require("./instant"); const queue_1 = require("./queue"); const defaults = { event: "data", error: "error", end: ["close", "end"], keepAlive: 0, debug: false, noSleep: false, }; function waitResponse(emitter, options, eventListener) { emitter.off(options.event, eventListener); return new Promise((resolve, reject) => { emitter.once(options.event, (value) => { emitter.on(options.event, eventListener); resolve({ value }); emitter.removeListener(options.error, reject); options.end.forEach((event) => emitter.removeListener(event, resolve)); }); emitter.once(options.error, reject); options.end.forEach((event) => emitter.once(event, resolve)); }); } function getInBetweenTimeoutRace(options, emitter, context, eventListener) { const timeoutWrapper = timeout_1.timeout(options.inBetweenTimeout, context); return () => [ waitResponse(emitter, options, eventListener), timeoutWrapper.awaiter, ]; } function getFirstAwaiter(options, emitter, context, eventListener) { if (options.firstEventTimeout) { const firstTimeout = timeout_1.timeout(options.firstEventTimeout, context); return Promise.race([ waitResponse(emitter, options, eventListener), firstTimeout.awaiter, ]); } return waitResponse(emitter, options, eventListener); } function switchRace(options, emitter, getNextRace, context, eventListener) { let timeoutRace; return () => timeoutRace ? timeoutRace() : [ getFirstAwaiter(options, emitter, context, eventListener).then((result) => { if (result !== timeout_1.timedOut) { timeoutRace = getNextRace(); } return result; }), ]; } function getTimeoutRace(options, emitter, context, eventListener) { return switchRace(options, emitter, () => getInBetweenTimeoutRace(options, emitter, context, eventListener), context, eventListener); } function raceFactory(options, emitter, context, eventListener) { if (options.inBetweenTimeout) { return getTimeoutRace(options, emitter, context, eventListener); } const getWaitResponse = () => [ waitResponse(emitter, options, eventListener), ]; return options.firstEventTimeout ? switchRace(options, emitter, () => getWaitResponse, context, eventListener) : getWaitResponse; } function forEmitOf(emitter, options) { if (!options) { options = defaults; } options = { ...defaults, ...options }; if (!(emitter instanceof events_1.EventEmitter)) { throw new Error("emitter must be a instance of EventEmitter"); } if (emitter.readableEnded || emitter.writableEnded) { throw new Error("stream has ended"); } if (options.transform) { if (typeof options.transform !== "function") { throw new Error("transform must be a function"); } } if (!Array.isArray(options.end)) { throw new Error("end must be an array"); } let events = queue_1.getQueue(); let error; let active = true; const context = { lastResultAt: 0, }; const eventListener = (event) => { context.lastResultAt = instant_1.instant(); return events.push(event); }; const endListener = () => { active = false; }; const errorListener = (err) => { error = err; }; const removeListeners = () => { events = queue_1.getQueue(); emitter.removeListener(options.event, eventListener); emitter.removeListener(options.error, errorListener); options.end.forEach((event) => emitter.removeListener(event, endListener)); }; emitter.on(options.event, eventListener); emitter.once(options.error, errorListener); options.end.forEach((event) => emitter.once(event, endListener)); const getRaceItems = raceFactory(options, emitter, context, eventListener); function generator() { let completed = false; let shouldYield = true; let countEvents = 0; let countKeepAlive = 0; const start = process.hrtime(); async function runReturn(value) { if (!completed) { shouldYield = false; completed = true; removeListeners(); debugging_1.debugIteratorReturn(options); } return { done: true, value }; } if (options.keepAlive && (!options.firstEventTimeout || !options.inBetweenTimeout)) { const keepAlive = () => { if (active && !error && (countEvents === 0 || !options.inBetweenTimeout)) { countKeepAlive = debugging_1.debugKeepAlive(options, countKeepAlive, start); setTimeout(keepAlive, options.keepAlive); } else { debugging_1.debugKeepAliveEnding(options, countKeepAlive, start); } }; setTimeout(keepAlive, options.keepAlive); } context.lastResultAt = instant_1.instant(); return { [Symbol.asyncIterator]() { return { async next() { if (error) { throw error; } if (shouldYield && !events.length && active) { debugging_1.debugRaceStart(options); const winner = await Promise.race(getRaceItems()); debugging_1.debugRaceEnd(options, winner); if (winner === timeout_1.timedOut) { removeListeners(); active = false; throw Error("Event timed out"); } if (winner) { return { done: false, value: options.transform ? options.transform(winner.value) : winner.value, }; } } if (!shouldYield || (events.length === 0 && !active)) { return runReturn(); } debugging_1.debugYielding(options, events); if (!options.noSleep) { await sleep_1.breath(); } const event = events.shift(); countEvents++; if (options.limit && countEvents >= options.limit) { debugging_1.debugYieldLimit(options); shouldYield = false; } return { done: false, value: options.transform ? options.transform(event) : event, }; }, return: runReturn, }; }, }; } return generator(); } module.exports = forEmitOf;