Skip to content

Commit 3841cbe

Browse files
authoredDec 15, 2024
feat: add async generator logic to ProcessPromise (google#984)
1 parent 9d28e65 commit 3841cbe

File tree

3 files changed

+123
-2
lines changed

3 files changed

+123
-2
lines changed
 

‎.size-limit.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22
{
33
"name": "zx/core",
44
"path": ["build/core.cjs", "build/util.cjs", "build/vendor-core.cjs"],
5-
"limit": "73 kB",
5+
"limit": "74 kB",
66
"brotli": false,
77
"gzip": false
88
},
99
{
1010
"name": "zx/index",
1111
"path": "build/*.{js,cjs}",
12-
"limit": "800 kB",
12+
"limit": "801 kB",
1313
"brotli": false,
1414
"gzip": false
1515
},

‎src/core.ts

+32
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,38 @@ export class ProcessPromise extends Promise<ProcessOutput> {
544544
return super.catch(onrejected)
545545
}
546546

547+
// Async iterator API
548+
async *[Symbol.asyncIterator]() {
549+
const _store = this._zurk!.store.stdout
550+
let _stream
551+
552+
if (_store.length) {
553+
_stream = VoidStream.from(_store)
554+
} else {
555+
_stream = this.stdout[Symbol.asyncIterator]
556+
? this.stdout
557+
: VoidStream.from(this.stdout)
558+
}
559+
560+
let buffer = ''
561+
562+
for await (const chunk of _stream) {
563+
const chunkStr = chunk.toString()
564+
buffer += chunkStr
565+
566+
let lines = buffer.split('\n')
567+
buffer = lines.pop() || ''
568+
569+
for (const line of lines) {
570+
yield line
571+
}
572+
}
573+
574+
if (buffer.length > 0) {
575+
yield buffer
576+
}
577+
}
578+
547579
// Stream-like API
548580
private writable = true
549581
private emit(event: string, ...args: any[]) {

‎test/core.test.js

+89
Original file line numberDiff line numberDiff line change
@@ -703,6 +703,95 @@ describe('core', () => {
703703
})
704704
})
705705

706+
describe('[Symbol.asyncIterator]', () => {
707+
it('should iterate over lines from stdout', async () => {
708+
const process = $`echo "Line1\nLine2\nLine3"`
709+
710+
const lines = []
711+
for await (const line of process) {
712+
lines.push(line)
713+
}
714+
715+
assert.equal(lines.length, 3, 'Should have 3 lines')
716+
assert.equal(lines[0], 'Line1', 'First line should be "Line1"')
717+
assert.equal(lines[1], 'Line2', 'Second line should be "Line2"')
718+
assert.equal(lines[2], 'Line3', 'Third line should be "Line3"')
719+
})
720+
721+
it('should handle partial lines correctly', async () => {
722+
const process = $`node -e "process.stdout.write('PartialLine1\\nLine2\\nPartial'); setTimeout(() => process.stdout.write('Line3\\n'), 100)"`
723+
724+
const lines = []
725+
for await (const line of process) {
726+
lines.push(line)
727+
}
728+
729+
assert.equal(lines.length, 3, 'Should have 3 lines')
730+
assert.equal(
731+
lines[0],
732+
'PartialLine1',
733+
'First line should be "PartialLine1"'
734+
)
735+
assert.equal(lines[1], 'Line2', 'Second line should be "Line2"')
736+
assert.equal(
737+
lines[2],
738+
'PartialLine3',
739+
'Third line should be "PartialLine3"'
740+
)
741+
})
742+
743+
it('should handle empty stdout', async () => {
744+
const process = $`echo -n ""`
745+
746+
const lines = []
747+
for await (const line of process) {
748+
lines.push(line)
749+
}
750+
751+
assert.equal(lines.length, 0, 'Should have 0 lines for empty stdout')
752+
})
753+
754+
it('should handle single line without trailing newline', async () => {
755+
const process = $`echo -n "SingleLine"`
756+
757+
const lines = []
758+
for await (const line of process) {
759+
lines.push(line)
760+
}
761+
762+
assert.equal(
763+
lines.length,
764+
1,
765+
'Should have 1 line for single line without trailing newline'
766+
)
767+
assert.equal(lines[0], 'SingleLine', 'The line should be "SingleLine"')
768+
})
769+
770+
it('should yield all buffered and new chunks when iterated after a delay', async () => {
771+
const process = $`sleep 0.1; echo Chunk1; sleep 0.2; echo Chunk2;`
772+
773+
const collectedChunks = []
774+
775+
await new Promise((resolve) => setTimeout(resolve, 400))
776+
777+
for await (const chunk of process) {
778+
collectedChunks.push(chunk)
779+
}
780+
781+
assert.equal(collectedChunks.length, 2, 'Should have received 2 chunks')
782+
assert.equal(
783+
collectedChunks[0],
784+
'Chunk1',
785+
'First chunk should be "Chunk1"'
786+
)
787+
assert.equal(
788+
collectedChunks[1],
789+
'Chunk2',
790+
'Second chunk should be "Chunk2"'
791+
)
792+
})
793+
})
794+
706795
test('quiet() mode is working', async () => {
707796
const log = console.log
708797
let stdout = ''

0 commit comments

Comments
 (0)