Skip to content

Commit 4869e5e

Browse files
feat: implement BodyReadable.bytes (#3391) (#3711)
(cherry picked from commit db8e642) Co-authored-by: tsctx <[email protected]>
1 parent 5be8ebf commit 4869e5e

File tree

9 files changed

+98
-14
lines changed

9 files changed

+98
-14
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ The `body` mixins are the most common way to format the request/response body. M
8484

8585
- [`.arrayBuffer()`](https://fetch.spec.whatwg.org/#dom-body-arraybuffer)
8686
- [`.blob()`](https://fetch.spec.whatwg.org/#dom-body-blob)
87+
- [`.bytes()`](https://fetch.spec.whatwg.org/#dom-body-bytes)
8788
- [`.json()`](https://fetch.spec.whatwg.org/#dom-body-json)
8889
- [`.text()`](https://fetch.spec.whatwg.org/#dom-body-text)
8990

docs/docs/api/Dispatcher.md

+7-5
Original file line numberDiff line numberDiff line change
@@ -488,11 +488,13 @@ The `RequestOptions.method` property should not be value `'CONNECT'`.
488488

489489
`body` contains the following additional [body mixin](https://fetch.spec.whatwg.org/#body-mixin) methods and properties:
490490

491-
- `text()`
492-
- `json()`
493-
- `arrayBuffer()`
494-
- `body`
495-
- `bodyUsed`
491+
* [`.arrayBuffer()`](https://fetch.spec.whatwg.org/#dom-body-arraybuffer)
492+
* [`.blob()`](https://fetch.spec.whatwg.org/#dom-body-blob)
493+
* [`.bytes()`](https://fetch.spec.whatwg.org/#dom-body-bytes)
494+
* [`.json()`](https://fetch.spec.whatwg.org/#dom-body-json)
495+
* [`.text()`](https://fetch.spec.whatwg.org/#dom-body-text)
496+
* `body`
497+
* `bodyUsed`
496498

497499
`body` can not be consumed twice. For example, calling `text()` after `json()` throws `TypeError`.
498500

docs/docs/api/Fetch.md

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ This API is implemented as per the standard, you can find documentation on [MDN]
2828

2929
- [`.arrayBuffer()`](https://fetch.spec.whatwg.org/#dom-body-arraybuffer)
3030
- [`.blob()`](https://fetch.spec.whatwg.org/#dom-body-blob)
31+
- [`.bytes()`](https://fetch.spec.whatwg.org/#dom-body-bytes)
3132
- [`.formData()`](https://fetch.spec.whatwg.org/#dom-body-formdata)
3233
- [`.json()`](https://fetch.spec.whatwg.org/#dom-body-json)
3334
- [`.text()`](https://fetch.spec.whatwg.org/#dom-body-text)

lib/api/readable.js

+33-9
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,11 @@ class BodyReadable extends Readable {
121121
return consume(this, 'blob')
122122
}
123123

124+
// https://fetch.spec.whatwg.org/#dom-body-bytes
125+
async bytes () {
126+
return consume(this, 'bytes')
127+
}
128+
124129
// https://fetch.spec.whatwg.org/#dom-body-arraybuffer
125130
async arrayBuffer () {
126131
return consume(this, 'arrayBuffer')
@@ -306,6 +311,31 @@ function chunksDecode (chunks, length) {
306311
return buffer.utf8Slice(start, bufferLength)
307312
}
308313

314+
/**
315+
* @param {Buffer[]} chunks
316+
* @param {number} length
317+
* @returns {Uint8Array}
318+
*/
319+
function chunksConcat (chunks, length) {
320+
if (chunks.length === 0 || length === 0) {
321+
return new Uint8Array(0)
322+
}
323+
if (chunks.length === 1) {
324+
// fast-path
325+
return new Uint8Array(chunks[0])
326+
}
327+
const buffer = new Uint8Array(Buffer.allocUnsafeSlow(length).buffer)
328+
329+
let offset = 0
330+
for (let i = 0; i < chunks.length; ++i) {
331+
const chunk = chunks[i]
332+
buffer.set(chunk, offset)
333+
offset += chunk.length
334+
}
335+
336+
return buffer
337+
}
338+
309339
function consumeEnd (consume) {
310340
const { type, body, resolve, stream, length } = consume
311341

@@ -315,17 +345,11 @@ function consumeEnd (consume) {
315345
} else if (type === 'json') {
316346
resolve(JSON.parse(chunksDecode(body, length)))
317347
} else if (type === 'arrayBuffer') {
318-
const dst = new Uint8Array(length)
319-
320-
let pos = 0
321-
for (const buf of body) {
322-
dst.set(buf, pos)
323-
pos += buf.byteLength
324-
}
325-
326-
resolve(dst.buffer)
348+
resolve(chunksConcat(body, length).buffer)
327349
} else if (type === 'blob') {
328350
resolve(new Blob(body, { type: stream[kContentType] }))
351+
} else if (type === 'bytes') {
352+
resolve(chunksConcat(body, length))
329353
}
330354

331355
consumeFinish(consume)

test/client-request.js

+26
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,32 @@ test('request arrayBuffer', async (t) => {
655655
await t.completed
656656
})
657657

658+
test('request bytes', async (t) => {
659+
t = tspl(t, { plan: 2 })
660+
661+
const obj = { asd: true }
662+
const server = createServer((req, res) => {
663+
res.end(JSON.stringify(obj))
664+
})
665+
after(() => server.close())
666+
667+
server.listen(0, async () => {
668+
const client = new Client(`http://localhost:${server.address().port}`)
669+
after(() => client.destroy())
670+
671+
const { body } = await client.request({
672+
path: '/',
673+
method: 'GET'
674+
})
675+
const bytes = await body.bytes()
676+
677+
t.deepStrictEqual(new TextEncoder().encode(JSON.stringify(obj)), bytes)
678+
t.ok(bytes instanceof Uint8Array)
679+
})
680+
681+
await t.completed
682+
})
683+
658684
test('request body', async (t) => {
659685
t = tspl(t, { plan: 1 })
660686

test/readable.js

+21
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,27 @@ describe('Readable', () => {
8383
t.deepStrictEqual(arrayBuffer, expected)
8484
})
8585

86+
test('.bytes()', async function (t) {
87+
t = tspl(t, { plan: 1 })
88+
89+
function resume () {
90+
}
91+
function abort () {
92+
}
93+
const r = new Readable({ resume, abort })
94+
95+
r.push(Buffer.from('hello'))
96+
r.push(Buffer.from(' world'))
97+
98+
process.nextTick(() => {
99+
r.push(null)
100+
})
101+
102+
const bytes = await r.bytes()
103+
104+
t.deepStrictEqual(bytes, new TextEncoder().encode('hello world'))
105+
})
106+
86107
test('.json()', async function (t) {
87108
t = tspl(t, { plan: 1 })
88109

test/types/readable.test-d.ts

+3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ expectAssignable<BodyReadable>(new BodyReadable())
2020
// blob
2121
expectAssignable<Promise<Blob>>(readable.blob())
2222

23+
// bytes
24+
expectAssignable<Promise<Uint8Array>>(readable.bytes())
25+
2326
// arrayBuffer
2427
expectAssignable<Promise<ArrayBuffer>>(readable.arrayBuffer())
2528

types/dispatcher.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ declare namespace Dispatcher {
244244
readonly bodyUsed: boolean;
245245
arrayBuffer(): Promise<ArrayBuffer>;
246246
blob(): Promise<Blob>;
247+
bytes(): Promise<Uint8Array>;
247248
formData(): Promise<never>;
248249
json(): Promise<unknown>;
249250
text(): Promise<string>;

types/readable.d.ts

+5
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ declare class BodyReadable extends Readable {
2525
*/
2626
blob(): Promise<Blob>
2727

28+
/** Consumes and returns the body as an Uint8Array
29+
* https://fetch.spec.whatwg.org/#dom-body-bytes
30+
*/
31+
bytes(): Promise<Uint8Array>
32+
2833
/** Consumes and returns the body as an ArrayBuffer
2934
* https://fetch.spec.whatwg.org/#dom-body-arraybuffer
3035
*/

0 commit comments

Comments
 (0)