mirror of
https://github.com/nodejs/node.git
synced 2026-10-28 10:15:07 +08:00
Just say no to `new Promise(() => timer(...))` use promisified timers Signed-off-by: James M Snell <jasnell@gmail.com> PR-URL: https://github.com/nodejs/node/pull/66079 Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
370 lines
9.7 KiB
JavaScript
370 lines
9.7 KiB
JavaScript
// Flags: --experimental-stream-iter
|
|
'use strict';
|
|
|
|
const common = require('../common');
|
|
const assert = require('assert');
|
|
const { pipeTo, pipeToSync, from, fromSync } = require('stream/iter');
|
|
const { setTimeout } = require('timers/promises');
|
|
|
|
async function testPipeToSync() {
|
|
const written = [];
|
|
const writer = {
|
|
writeSync(chunk) { written.push(chunk); return true; },
|
|
endSync() { return written.length; },
|
|
fail() {},
|
|
};
|
|
|
|
const totalBytes = pipeToSync(fromSync('pipe-data'), writer);
|
|
assert.strictEqual(totalBytes, 9); // 'pipe-data' = 9 UTF-8 bytes
|
|
assert.ok(written.length > 0);
|
|
const result = new TextDecoder().decode(
|
|
new Uint8Array(written.reduce((acc, c) => [...acc, ...c], [])));
|
|
assert.strictEqual(result, 'pipe-data');
|
|
}
|
|
|
|
async function testPipeTo() {
|
|
const written = [];
|
|
const writer = {
|
|
async write(chunk) { written.push(chunk); },
|
|
async end() { return written.length; },
|
|
async fail() {},
|
|
};
|
|
|
|
const totalBytes = await pipeTo(from('async-pipe-data'), writer);
|
|
assert.strictEqual(totalBytes, 15); // 'async-pipe-data' = 15 UTF-8 bytes
|
|
assert.ok(written.length > 0);
|
|
}
|
|
|
|
async function testPipeToPreventClose() {
|
|
let endCalled = false;
|
|
const writer = {
|
|
async write() {},
|
|
async end() { endCalled = true; },
|
|
async fail() {},
|
|
};
|
|
|
|
await pipeTo(from('data'), writer, { preventClose: true });
|
|
assert.strictEqual(endCalled, false);
|
|
}
|
|
|
|
// PipeTo source error calls writer.fail()
|
|
async function testPipeToSourceError() {
|
|
let failCalled = false;
|
|
let failReason;
|
|
const writer = {
|
|
write() {},
|
|
fail(reason) { failCalled = true; failReason = reason; },
|
|
};
|
|
async function* failingSource() {
|
|
yield [new TextEncoder().encode('a')];
|
|
throw new Error('pipe source boom');
|
|
}
|
|
await assert.rejects(
|
|
() => pipeTo(failingSource(), writer),
|
|
{ message: 'pipe source boom' },
|
|
);
|
|
assert.strictEqual(failCalled, true);
|
|
assert.strictEqual(failReason.message, 'pipe source boom');
|
|
}
|
|
|
|
// PipeToSync source error calls writer.fail()
|
|
async function testPipeToSyncSourceError() {
|
|
let failCalled = false;
|
|
const writer = {
|
|
writeSync() { return true; },
|
|
endSync: common.mustNotCall(),
|
|
fail(reason) { failCalled = true; },
|
|
};
|
|
function* failingSource() {
|
|
yield [new TextEncoder().encode('a')];
|
|
throw new Error('sync pipe boom');
|
|
}
|
|
assert.throws(
|
|
() => pipeToSync(failingSource(), writer),
|
|
{ message: 'sync pipe boom' },
|
|
);
|
|
assert.strictEqual(failCalled, true);
|
|
}
|
|
|
|
// PipeTo with AbortSignal
|
|
async function testPipeToWithSignal() {
|
|
const ac = new AbortController();
|
|
const chunks = [];
|
|
const writer = {
|
|
write(chunk) { chunks.push(chunk); },
|
|
};
|
|
async function* slowSource() {
|
|
yield [new TextEncoder().encode('a')];
|
|
await setTimeout(50);
|
|
yield [new TextEncoder().encode('b')];
|
|
}
|
|
ac.abort();
|
|
await assert.rejects(
|
|
() => pipeTo(slowSource(), writer, { signal: ac.signal }),
|
|
{ name: 'AbortError' },
|
|
);
|
|
}
|
|
|
|
// PipeTo with transforms
|
|
async function testPipeToWithTransforms() {
|
|
const chunks = [];
|
|
const writer = {
|
|
write(chunk) { chunks.push(new TextDecoder().decode(chunk)); },
|
|
};
|
|
const upper = (batch) => {
|
|
if (batch === null) return null;
|
|
return batch.map((c) => {
|
|
const out = new Uint8Array(c);
|
|
for (let i = 0; i < out.length; i++)
|
|
out[i] -= (out[i] >= 97 && out[i] <= 122) * 32;
|
|
return out;
|
|
});
|
|
};
|
|
await pipeTo(from('hello'), upper, writer);
|
|
assert.strictEqual(chunks.join(''), 'HELLO');
|
|
}
|
|
|
|
// PipeToSync with transforms
|
|
async function testPipeToSyncWithTransforms() {
|
|
const chunks = [];
|
|
const writer = {
|
|
writeSync(chunk) { chunks.push(new TextDecoder().decode(chunk)); return true; },
|
|
endSync() { return 0; },
|
|
};
|
|
const upper = (batch) => {
|
|
if (batch === null) return null;
|
|
return batch.map((c) => {
|
|
const out = new Uint8Array(c);
|
|
for (let i = 0; i < out.length; i++)
|
|
out[i] -= (out[i] >= 97 && out[i] <= 122) * 32;
|
|
return out;
|
|
});
|
|
};
|
|
pipeToSync(fromSync('hello'), upper, writer);
|
|
assert.strictEqual(chunks.join(''), 'HELLO');
|
|
}
|
|
|
|
async function testPipeToWriterTransformMethodIgnored() {
|
|
const chunks = [];
|
|
const writer = {
|
|
transform: common.mustNotCall(),
|
|
write(chunk) { chunks.push(new TextDecoder().decode(chunk)); },
|
|
};
|
|
|
|
await pipeTo(from('hello'), writer);
|
|
assert.strictEqual(chunks.join(''), 'hello');
|
|
}
|
|
|
|
async function testPipeToSyncWriterTransformMethodIgnored() {
|
|
const chunks = [];
|
|
const writer = {
|
|
transform: common.mustNotCall(),
|
|
writeSync(chunk) {
|
|
chunks.push(new TextDecoder().decode(chunk));
|
|
return true;
|
|
},
|
|
endSync() { return 0; },
|
|
};
|
|
|
|
pipeToSync(fromSync('hello'), writer);
|
|
assert.strictEqual(chunks.join(''), 'hello');
|
|
}
|
|
|
|
// PipeTo with writev writer
|
|
async function testPipeToWithWritevWriter() {
|
|
const allChunks = [];
|
|
const writer = {
|
|
write(chunk) { allChunks.push(chunk); },
|
|
writev(chunks) { allChunks.push(...chunks); },
|
|
};
|
|
await pipeTo(from('hello world'), writer);
|
|
assert.strictEqual(allChunks.length > 0, true);
|
|
}
|
|
|
|
// PipeTo with writeSync/writevSync fallback
|
|
async function testPipeToSyncFallback() {
|
|
const chunks = [];
|
|
const writer = {
|
|
writeSync(chunk) { chunks.push(chunk); return true; },
|
|
write(chunk) { chunks.push(chunk); },
|
|
};
|
|
await pipeTo(from('hello'), writer);
|
|
assert.strictEqual(chunks.length > 0, true);
|
|
}
|
|
|
|
// PipeTo preventFail option
|
|
async function testPipeToPreventFail() {
|
|
let failCalled = false;
|
|
const writer = {
|
|
write() {},
|
|
fail() { failCalled = true; },
|
|
};
|
|
// eslint-disable-next-line require-yield
|
|
async function* failingSource() {
|
|
throw new Error('boom');
|
|
}
|
|
await assert.rejects(
|
|
() => pipeTo(failingSource(), writer, { preventFail: true }),
|
|
{ message: 'boom' },
|
|
);
|
|
assert.strictEqual(failCalled, false);
|
|
}
|
|
|
|
// PipeToSync preventClose option
|
|
async function testPipeToSyncPreventClose() {
|
|
let endCalled = false;
|
|
const writer = {
|
|
writeSync() { return true; },
|
|
endSync() { endCalled = true; return 0; },
|
|
};
|
|
pipeToSync(fromSync('hello'), writer, { preventClose: true });
|
|
assert.strictEqual(endCalled, false);
|
|
}
|
|
|
|
// Regression test: pipeTo should work with a minimal writer that only
|
|
// implements write(). end(), fail(), and all *Sync methods are optional.
|
|
async function testPipeToMinimalWriter() {
|
|
const chunks = [];
|
|
const minimalWriter = {
|
|
write(chunk) {
|
|
chunks.push(chunk);
|
|
},
|
|
};
|
|
|
|
await pipeTo(from('minimal'), minimalWriter);
|
|
assert.strictEqual(chunks.length > 0, true);
|
|
}
|
|
|
|
async function testPipeToSyncMinimalWriter() {
|
|
const chunks = [];
|
|
const minimalWriter = {
|
|
writeSync(chunk) {
|
|
chunks.push(chunk);
|
|
return true;
|
|
},
|
|
};
|
|
|
|
pipeToSync(fromSync('minimal-sync'), minimalWriter, { preventClose: true });
|
|
assert.strictEqual(chunks.length > 0, true);
|
|
}
|
|
|
|
async function testPipeToSyncIterableUsesFromBatching() {
|
|
let pulled = 0;
|
|
let firstWritePulled = 0;
|
|
const chunks = [];
|
|
function* source() {
|
|
for (let i = 0; i < 3; i++) {
|
|
pulled++;
|
|
yield new Uint8Array([0x61 + i]);
|
|
}
|
|
}
|
|
const writer = {
|
|
write: common.mustNotCall(),
|
|
writeSync(chunk) {
|
|
if (firstWritePulled === 0) {
|
|
firstWritePulled = pulled;
|
|
}
|
|
chunks.push(chunk);
|
|
return true;
|
|
},
|
|
};
|
|
|
|
const totalBytes = await pipeTo(source(), writer);
|
|
assert.strictEqual(totalBytes, 3);
|
|
assert.strictEqual(firstWritePulled, 3);
|
|
assert.deepStrictEqual(chunks, [
|
|
new Uint8Array([0x61]),
|
|
new Uint8Array([0x62]),
|
|
new Uint8Array([0x63]),
|
|
]);
|
|
}
|
|
|
|
async function testPipeToSourceNormalizationIndependentOfWriter() {
|
|
function source() {
|
|
return {
|
|
*[Symbol.iterator]() {
|
|
yield {
|
|
async *[Symbol.asyncIterator]() {
|
|
yield 'nested';
|
|
},
|
|
};
|
|
},
|
|
};
|
|
}
|
|
|
|
for (const hasWriteSync of [false, true]) {
|
|
const writer = { write: common.mustNotCall() };
|
|
if (hasWriteSync) writer.writeSync = common.mustNotCall();
|
|
|
|
await assert.rejects(
|
|
pipeTo(source(), writer, { preventClose: true, preventFail: true }),
|
|
{ code: 'ERR_INVALID_ARG_TYPE' },
|
|
);
|
|
}
|
|
}
|
|
|
|
async function testPipeToSyncIterableWriteFallback() {
|
|
const asyncWrites = [];
|
|
const writer = {
|
|
writeSync(chunk) {
|
|
return chunk[0] !== 0x62;
|
|
},
|
|
async write(chunk) {
|
|
asyncWrites.push(chunk);
|
|
},
|
|
};
|
|
function* source() {
|
|
yield new Uint8Array([0x61]);
|
|
yield new Uint8Array([0x62]);
|
|
yield new Uint8Array([0x63]);
|
|
}
|
|
|
|
const totalBytes = await pipeTo(source(), writer);
|
|
assert.strictEqual(totalBytes, 3);
|
|
assert.deepStrictEqual(asyncWrites, [new Uint8Array([0x62])]);
|
|
}
|
|
|
|
async function testPipeToSyncIterableAsyncValue() {
|
|
const chunks = [];
|
|
const writer = {
|
|
write: common.mustNotCall(),
|
|
writeSync(chunk) {
|
|
chunks.push(chunk);
|
|
return true;
|
|
},
|
|
};
|
|
function* source() {
|
|
yield Promise.resolve('a');
|
|
yield new Uint8Array([0x62]);
|
|
}
|
|
|
|
const totalBytes = await pipeTo(source(), writer);
|
|
assert.strictEqual(totalBytes, 2);
|
|
const result = new TextDecoder().decode(
|
|
new Uint8Array(chunks.reduce((acc, c) => [...acc, ...c], [])));
|
|
assert.strictEqual(result, 'ab');
|
|
}
|
|
|
|
Promise.all([
|
|
testPipeToSync(),
|
|
testPipeTo(),
|
|
testPipeToPreventClose(),
|
|
testPipeToSourceError(),
|
|
testPipeToSyncSourceError(),
|
|
testPipeToWithSignal(),
|
|
testPipeToWithTransforms(),
|
|
testPipeToSyncWithTransforms(),
|
|
testPipeToWriterTransformMethodIgnored(),
|
|
testPipeToSyncWriterTransformMethodIgnored(),
|
|
testPipeToWithWritevWriter(),
|
|
testPipeToSyncFallback(),
|
|
testPipeToPreventFail(),
|
|
testPipeToSyncPreventClose(),
|
|
testPipeToMinimalWriter(),
|
|
testPipeToSyncMinimalWriter(),
|
|
testPipeToSyncIterableUsesFromBatching(),
|
|
testPipeToSyncIterableWriteFallback(),
|
|
testPipeToSyncIterableAsyncValue(),
|
|
testPipeToSourceNormalizationIndependentOfWriter(),
|
|
]).then(common.mustCall());
|