mirror of
https://github.com/oven-sh/bun
synced 2026-02-10 10:58:56 +00:00
events.on: Add compatibility with readline module (#8885)
* fix: make event.on work with for...await of readline module * fix: queueing events before next() call and multiple event vals * test: add tests for events.on and readline for await...of * style, docs * [autofix.ci] apply automated fixes * refactor: use removeListeners instead of check for done * test: merge test files * test: remove todo comment * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
This commit is contained in:
@@ -65,22 +65,6 @@ describe("node:events", () => {
|
||||
await promise;
|
||||
expect(emitter.listenerCount("hey")).toBe(0);
|
||||
});
|
||||
|
||||
// TODO: extensive events.on tests
|
||||
// test("on", () => {
|
||||
// const emitter = new EventEmitter();
|
||||
// const asyncIterator = EventEmitter.on(emitter, "hey");
|
||||
|
||||
// expect(asyncIterator.next).toBeDefined();
|
||||
// expect(asyncIterator[Symbol.asyncIterator]).toBeDefined();
|
||||
|
||||
// const fn = async () => {
|
||||
// const { value } = await asyncIterator.next();
|
||||
// expect(value).toBe(1);
|
||||
// };
|
||||
|
||||
// emitter.emit("hey", 1, 2, 3);
|
||||
// });
|
||||
});
|
||||
|
||||
describe("EventEmitter", () => {
|
||||
@@ -422,6 +406,207 @@ describe("EventEmitter", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("EventEmitter.on", () => {
|
||||
test("Basic test", async () => {
|
||||
const emitter = new EventEmitter();
|
||||
const asyncIterator = EventEmitter.on(emitter, "hey");
|
||||
|
||||
expect(asyncIterator.next).toBeDefined();
|
||||
expect(asyncIterator[Symbol.asyncIterator]).toBeDefined();
|
||||
|
||||
process.nextTick(() => {
|
||||
emitter.emit("hey", 1);
|
||||
});
|
||||
|
||||
const { value } = await asyncIterator.next();
|
||||
expect(value).toEqual([1]);
|
||||
});
|
||||
|
||||
test("Basic test with for await...of", async () => {
|
||||
const emitter = new EventEmitter();
|
||||
const asyncIterator = EventEmitter.on(emitter, "hey", { close: ["close"] } as any);
|
||||
|
||||
process.nextTick(() => {
|
||||
emitter.emit("hey", 1);
|
||||
emitter.emit("hey", 2);
|
||||
emitter.emit("hey", 3);
|
||||
emitter.emit("hey", 4);
|
||||
emitter.emit("close");
|
||||
});
|
||||
|
||||
const result = [];
|
||||
for await (const ev of asyncIterator) {
|
||||
result.push(ev);
|
||||
}
|
||||
|
||||
expect(result).toEqual([[1], [2], [3], [4]]);
|
||||
});
|
||||
|
||||
test("Stop reading events after 'close' event is emitted", async () => {
|
||||
const emitter = new EventEmitter();
|
||||
const asyncIterator = EventEmitter.on(emitter, "hey", { close: ["close"] } as any);
|
||||
|
||||
process.nextTick(() => {
|
||||
emitter.emit("hey", 1);
|
||||
emitter.emit("hey", 2);
|
||||
emitter.emit("close");
|
||||
emitter.emit("hey", 3);
|
||||
});
|
||||
|
||||
const result = [];
|
||||
for await (const ev of asyncIterator) {
|
||||
result.push(ev);
|
||||
}
|
||||
|
||||
expect(result).toEqual([[1], [2]]);
|
||||
});
|
||||
|
||||
test("Queue events before first next() call", async () => {
|
||||
const emitter = new EventEmitter();
|
||||
const asyncIterator = EventEmitter.on(emitter, "hey");
|
||||
|
||||
emitter.emit("hey", 1);
|
||||
emitter.emit("hey", 2);
|
||||
emitter.emit("hey", 3);
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
expect((await asyncIterator.next()).value).toEqual([1]);
|
||||
expect((await asyncIterator.next()).value).toEqual([2]);
|
||||
expect((await asyncIterator.next()).value).toEqual([3]);
|
||||
});
|
||||
|
||||
test("Emit multiple values", async () => {
|
||||
const emitter = new EventEmitter();
|
||||
const asyncIterator = EventEmitter.on(emitter, "hey");
|
||||
|
||||
emitter.emit("hey", 1, 2, 3);
|
||||
|
||||
const { value } = await asyncIterator.next();
|
||||
expect(value).toEqual([1, 2, 3]);
|
||||
});
|
||||
|
||||
test("kFirstEventParam", async () => {
|
||||
const kFirstEventParam = Symbol.for("nodejs.kFirstEventParam");
|
||||
const emitter = new EventEmitter();
|
||||
const asyncIterator = EventEmitter.on(emitter, "hey", { [kFirstEventParam]: true } as any);
|
||||
|
||||
emitter.emit("hey", 1, 2, 3);
|
||||
emitter.emit("hey", [4, 5, 6]);
|
||||
|
||||
expect((await asyncIterator.next()).value).toBe(1);
|
||||
expect((await asyncIterator.next()).value).toEqual([4, 5, 6]);
|
||||
});
|
||||
|
||||
test("Cancel via error event", async () => {
|
||||
const { on, EventEmitter } = require("node:events");
|
||||
const process = require("node:process");
|
||||
|
||||
const ee = new EventEmitter();
|
||||
const output = [];
|
||||
|
||||
// Emit later on
|
||||
process.nextTick(() => {
|
||||
ee.emit("foo", "bar");
|
||||
ee.emit("foo", 42);
|
||||
ee.emit("foo", "baz");
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
ee.emit("error", "DONE");
|
||||
}, 1_000);
|
||||
|
||||
try {
|
||||
for await (const event of on(ee, "foo")) {
|
||||
output.push([1, event]);
|
||||
}
|
||||
} catch (error) {
|
||||
output.push([2, error]);
|
||||
}
|
||||
|
||||
expect(output).toEqual([
|
||||
[1, ["bar"]],
|
||||
[1, [42]],
|
||||
[1, ["baz"]],
|
||||
[2, "DONE"],
|
||||
]);
|
||||
});
|
||||
|
||||
test("AbortController", () => {
|
||||
const { on, EventEmitter } = require("node:events");
|
||||
|
||||
const ac = new AbortController();
|
||||
const ee = new EventEmitter();
|
||||
const output = [];
|
||||
|
||||
process.nextTick(() => {
|
||||
ee.emit("foo", "bar");
|
||||
ee.emit("foo", 42);
|
||||
ee.emit("foo", "baz");
|
||||
});
|
||||
(async () => {
|
||||
try {
|
||||
for await (const event of on(ee, "foo", { signal: ac.signal })) {
|
||||
output.push([1, event]);
|
||||
}
|
||||
console.log("unreachable");
|
||||
} catch (error: any) {
|
||||
const { code, message } = error;
|
||||
output.push([2, { code, message }]);
|
||||
|
||||
expect(output).toEqual([
|
||||
[1, ["bar"]],
|
||||
[1, [42]],
|
||||
[1, ["baz"]],
|
||||
[
|
||||
2,
|
||||
{
|
||||
code: "ABORT_ERR",
|
||||
message: "The operation was aborted",
|
||||
},
|
||||
],
|
||||
]);
|
||||
}
|
||||
})();
|
||||
|
||||
process.nextTick(() => ac.abort());
|
||||
});
|
||||
|
||||
// Checks for potential issues with FixedQueue size
|
||||
test("Queue many events", async () => {
|
||||
const emitter = new EventEmitter();
|
||||
const asyncIterator = EventEmitter.on(emitter, "hey");
|
||||
|
||||
for (let i = 0; i < 2500; i += 1) {
|
||||
emitter.emit("hey", i);
|
||||
}
|
||||
|
||||
expect((await asyncIterator.next()).value).toEqual([0]);
|
||||
});
|
||||
|
||||
test("readline.createInterface", async () => {
|
||||
const { createInterface } = require("node:readline");
|
||||
const { createReadStream } = require("node:fs");
|
||||
const path = require("node:path");
|
||||
|
||||
const fpath = path.join(__filename, "..", "..", "child_process", "fixtures", "child-process-echo-options.js");
|
||||
console.log(fpath);
|
||||
const interfaced = createInterface(createReadStream(fpath));
|
||||
const output = [];
|
||||
|
||||
try {
|
||||
for await (const line of interfaced) {
|
||||
output.push(line);
|
||||
}
|
||||
} catch (e) {
|
||||
expect(output).toBe([
|
||||
"// TODO - bun has no `send` method in the process",
|
||||
"process?.send({ env: process.env });",
|
||||
]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("EventEmitter error handling", () => {
|
||||
test("unhandled error event throws on emit", () => {
|
||||
const myEmitter = new EventEmitter();
|
||||
@@ -630,4 +815,9 @@ describe("EventEmitter constructors", () => {
|
||||
const events = req("events");
|
||||
new events();
|
||||
});
|
||||
|
||||
test("in cjs, events is callable", () => {
|
||||
const EventEmitter = require("events");
|
||||
new EventEmitter();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
test("in cjs, events is callable", () => {
|
||||
const EventEmitter = require("events");
|
||||
new EventEmitter();
|
||||
});
|
||||
|
||||
test("events.on", async () => {
|
||||
const { on, EventEmitter } = require("node:events");
|
||||
const process = require("node:process");
|
||||
|
||||
const ee = new EventEmitter();
|
||||
const output = [];
|
||||
|
||||
// Emit later on
|
||||
process.nextTick(() => {
|
||||
ee.emit("foo", "bar");
|
||||
ee.emit("foo", 42);
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
ee.emit("error", "DONE");
|
||||
}, 1_000);
|
||||
|
||||
try {
|
||||
for await (const event of on(ee, "foo")) {
|
||||
output.push([1, event]);
|
||||
}
|
||||
} catch (error) {
|
||||
output.push([2, error]);
|
||||
}
|
||||
|
||||
expect(output).toEqual([
|
||||
[1, ["bar"]],
|
||||
[1, [42]],
|
||||
[2, "DONE"],
|
||||
]);
|
||||
});
|
||||
|
||||
test("events.on with AbortController", () => {
|
||||
const { on, EventEmitter } = require("node:events");
|
||||
|
||||
const ac = new AbortController();
|
||||
const ee = new EventEmitter();
|
||||
const output = [];
|
||||
|
||||
process.nextTick(() => {
|
||||
ee.emit("foo", "bar");
|
||||
ee.emit("foo", 42);
|
||||
});
|
||||
(async () => {
|
||||
try {
|
||||
for await (const event of on(ee, "foo", { signal: ac.signal })) {
|
||||
output.push([1, event]);
|
||||
}
|
||||
console.log("unreachable");
|
||||
} catch (error) {
|
||||
const { code, message } = error;
|
||||
output.push([2, { code, message }]);
|
||||
|
||||
expect(output).toEqual([
|
||||
[1, ["bar"]],
|
||||
[1, [42]],
|
||||
[
|
||||
2,
|
||||
{
|
||||
code: "ABORT_ERR",
|
||||
message: "The operation was aborted",
|
||||
},
|
||||
],
|
||||
]);
|
||||
}
|
||||
})();
|
||||
|
||||
process.nextTick(() => ac.abort());
|
||||
});
|
||||
|
||||
test("readline.createInterface", async () => {
|
||||
const { createInterface } = require("node:readline");
|
||||
const { createReadStream } = require("node:fs");
|
||||
const path = require("node:path");
|
||||
|
||||
const fpath = path.join(__filename, "..", "..", "child_process", "fixtures", "child-process-echo-options.js");
|
||||
console.log(fpath);
|
||||
const interfaced = createInterface(createReadStream(fpath));
|
||||
const output = [];
|
||||
|
||||
try {
|
||||
for await (const line of interfaced) {
|
||||
output.push(line);
|
||||
}
|
||||
} catch (e) {
|
||||
expect(output).toBe(["// TODO - bun has no `send` method in the process", "process?.send({ env: process.env });"]);
|
||||
}
|
||||
});
|
||||
@@ -1880,6 +1880,23 @@ describe("readline.createInterface()", () => {
|
||||
input.write("abc\n");
|
||||
});
|
||||
|
||||
it("should support reading-in lines via for await...of loop", async () => {
|
||||
const sampleTextBuffer = new Buffer.from("Line1\nLine2\nLine3\nLine4");
|
||||
const bufferStream = new PassThrough();
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: bufferStream,
|
||||
});
|
||||
|
||||
process.nextTick(() => {
|
||||
bufferStream.end(sampleTextBuffer);
|
||||
});
|
||||
|
||||
const result = [];
|
||||
for await (const line of rl) result.push(line);
|
||||
expect(result).toEqual(["Line1", "Line2", "Line3", "Line4"]);
|
||||
});
|
||||
|
||||
it("should respond to home and end sequences for common pttys ", () => {
|
||||
const input = new PassThrough();
|
||||
const rl = readline.createInterface({
|
||||
|
||||
Reference in New Issue
Block a user