for-emit-of
Version:
Turn Node.js Events into Async Iterables
200 lines (199 loc) • 7.64 kB
JavaScript
;
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;