Skip to content

Commit 4dc123a

Browse files
Add ReadableStreamBYOBReader.prototype.read(view, { min })
When `read(view)` is called with the `min` option, the read will only be fulfilled as soon as `min` number of elements are available in the stream. Fixes #1143, and fixes #1175.
1 parent d58a68c commit 4dc123a

File tree

6 files changed

+180
-58
lines changed

6 files changed

+180
-58
lines changed

index.bs

Lines changed: 132 additions & 39 deletions
Large diffs are not rendered by default.

reference-implementation/lib/ReadableByteStreamController-impl.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ exports.implementation = class ReadableByteStreamControllerImpl {
8787
byteOffset: 0,
8888
byteLength: autoAllocateChunkSize,
8989
bytesFilled: 0,
90+
minimumFill: 1,
9091
elementSize: 1,
9192
viewConstructor: Uint8Array,
9293
readerType: 'default'

reference-implementation/lib/ReadableStreamBYOBReader-impl.js

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class ReadableStreamBYOBReaderImpl {
1111
aos.SetUpReadableStreamBYOBReader(this, stream);
1212
}
1313

14-
read(view) {
14+
read(view, options) {
1515
if (view.byteLength === 0) {
1616
return promiseRejectedWith(new TypeError('view must have non-zero byteLength'));
1717
}
@@ -22,6 +22,23 @@ class ReadableStreamBYOBReaderImpl {
2222
return promiseRejectedWith(new TypeError('view\'s buffer has been detached'));
2323
}
2424

25+
if (options.min === 0) {
26+
return promiseRejectedWith(
27+
new TypeError('options.min must be greater than 0')
28+
);
29+
}
30+
if (view.constructor !== DataView) {
31+
if (options.min > view.length) {
32+
return promiseRejectedWith(
33+
new RangeError('options.min must be less than or equal to view\'s length')
34+
);
35+
}
36+
} else if (options.min > view.byteLength) {
37+
return promiseRejectedWith(
38+
new RangeError('options.min must be less than or equal to view\'s byteLength')
39+
);
40+
}
41+
2542
if (this._stream === undefined) {
2643
return promiseRejectedWith(readerLockException('read'));
2744
}
@@ -32,7 +49,7 @@ class ReadableStreamBYOBReaderImpl {
3249
closeSteps: chunk => resolvePromise(promise, { value: chunk, done: true }),
3350
errorSteps: e => rejectPromise(promise, e)
3451
};
35-
aos.ReadableStreamBYOBReaderRead(this, view, readIntoRequest);
52+
aos.ReadableStreamBYOBReaderRead(this, view, options.min, readIntoRequest);
3653
return promise;
3754
}
3855

reference-implementation/lib/ReadableStreamBYOBReader.webidl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22
interface ReadableStreamBYOBReader {
33
constructor(ReadableStream stream);
44

5-
Promise<ReadableStreamReadResult> read(ArrayBufferView view);
5+
Promise<ReadableStreamReadResult> read(ArrayBufferView view, optional ReadableStreamBYOBReaderReadOptions options = {});
66
undefined releaseLock();
77
};
88
ReadableStreamBYOBReader includes ReadableStreamGenericReader;
9+
10+
dictionary ReadableStreamBYOBReaderReadOptions {
11+
[EnforceRange] unsigned long long min = 1;
12+
};

reference-implementation/lib/abstract-ops/readable-streams.js

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -647,7 +647,7 @@ function ReadableByteStreamTee(stream) {
647647
reading = false;
648648
}
649649
};
650-
ReadableStreamBYOBReaderRead(reader, view, readIntoRequest);
650+
ReadableStreamBYOBReaderRead(reader, view, 1, readIntoRequest);
651651
}
652652

653653
function pull1Algorithm() {
@@ -913,7 +913,7 @@ function ReadableStreamReaderGenericRelease(reader) {
913913
reader._stream = undefined;
914914
}
915915

916-
function ReadableStreamBYOBReaderRead(reader, view, readIntoRequest) {
916+
function ReadableStreamBYOBReaderRead(reader, view, min, readIntoRequest) {
917917
const stream = reader._stream;
918918

919919
assert(stream !== undefined);
@@ -923,7 +923,7 @@ function ReadableStreamBYOBReaderRead(reader, view, readIntoRequest) {
923923
if (stream._state === 'errored') {
924924
readIntoRequest.errorSteps(stream._storedError);
925925
} else {
926-
ReadableByteStreamControllerPullInto(stream._controller, view, readIntoRequest);
926+
ReadableByteStreamControllerPullInto(stream._controller, view, min, readIntoRequest);
927927
}
928928
}
929929

@@ -1272,7 +1272,7 @@ function ReadableByteStreamControllerClose(controller) {
12721272

12731273
if (controller._pendingPullIntos.length > 0) {
12741274
const firstPendingPullInto = controller._pendingPullIntos[0];
1275-
if (firstPendingPullInto.bytesFilled > 0) {
1275+
if (firstPendingPullInto.bytesFilled % firstPendingPullInto.elementSize !== 0) {
12761276
const e = new TypeError('Insufficient bytes to fill elements in the given buffer');
12771277
ReadableByteStreamControllerError(controller, e);
12781278

@@ -1290,7 +1290,7 @@ function ReadableByteStreamControllerCommitPullIntoDescriptor(stream, pullIntoDe
12901290

12911291
let done = false;
12921292
if (stream._state === 'closed') {
1293-
assert(pullIntoDescriptor.bytesFilled === 0);
1293+
assert(pullIntoDescriptor.bytesFilled % pullIntoDescriptor.elementSize === 0);
12941294
done = true;
12951295
}
12961296

@@ -1419,18 +1419,18 @@ function ReadableByteStreamControllerFillHeadPullIntoDescriptor(controller, size
14191419
}
14201420

14211421
function ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(controller, pullIntoDescriptor) {
1422-
const elementSize = pullIntoDescriptor.elementSize;
1423-
1424-
const currentAlignedBytes = pullIntoDescriptor.bytesFilled - pullIntoDescriptor.bytesFilled % elementSize;
1425-
14261422
const maxBytesToCopy = Math.min(controller._queueTotalSize,
14271423
pullIntoDescriptor.byteLength - pullIntoDescriptor.bytesFilled);
14281424
const maxBytesFilled = pullIntoDescriptor.bytesFilled + maxBytesToCopy;
1429-
const maxAlignedBytes = maxBytesFilled - maxBytesFilled % elementSize;
14301425

14311426
let totalBytesToCopyRemaining = maxBytesToCopy;
14321427
let ready = false;
1433-
if (maxAlignedBytes > currentAlignedBytes) {
1428+
assert(pullIntoDescriptor.bytesFilled < pullIntoDescriptor.minimumFill);
1429+
const remainderBytes = maxBytesFilled % pullIntoDescriptor.elementSize;
1430+
const maxAlignedBytes = maxBytesFilled - remainderBytes;
1431+
// A descriptor for a read() request that is not yet filled up to its minimum length will stay at the head
1432+
// of the queue, so the underlying source can keep filling it.
1433+
if (maxAlignedBytes >= pullIntoDescriptor.minimumFill) {
14341434
totalBytesToCopyRemaining = maxAlignedBytes - pullIntoDescriptor.bytesFilled;
14351435
ready = true;
14361436
}
@@ -1461,7 +1461,7 @@ function ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(controller,
14611461
if (ready === false) {
14621462
assert(controller._queueTotalSize === 0);
14631463
assert(pullIntoDescriptor.bytesFilled > 0);
1464-
assert(pullIntoDescriptor.bytesFilled < pullIntoDescriptor.elementSize);
1464+
assert(pullIntoDescriptor.bytesFilled < pullIntoDescriptor.minimumFill);
14651465
}
14661466

14671467
return ready;
@@ -1563,14 +1563,18 @@ function ReadableByteStreamControllerProcessReadRequestsUsingQueue(controller) {
15631563
}
15641564
}
15651565

1566-
function ReadableByteStreamControllerPullInto(controller, view, readIntoRequest) {
1566+
function ReadableByteStreamControllerPullInto(controller, view, min, readIntoRequest) {
15671567
const stream = controller._stream;
15681568

15691569
let elementSize = 1;
15701570
if (view.constructor !== DataView) {
15711571
elementSize = view.constructor.BYTES_PER_ELEMENT;
15721572
}
15731573

1574+
const minimumFill = min * elementSize;
1575+
assert(minimumFill >= elementSize && minimumFill <= view.byteLength);
1576+
assert(minimumFill % elementSize === 0);
1577+
15741578
const ctor = view.constructor;
15751579

15761580
let buffer;
@@ -1587,6 +1591,7 @@ function ReadableByteStreamControllerPullInto(controller, view, readIntoRequest)
15871591
byteOffset: view.byteOffset,
15881592
byteLength: view.byteLength,
15891593
bytesFilled: 0,
1594+
minimumFill,
15901595
elementSize,
15911596
viewConstructor: ctor,
15921597
readerType: 'byob'
@@ -1660,7 +1665,7 @@ function ReadableByteStreamControllerRespond(controller, bytesWritten) {
16601665
}
16611666

16621667
function ReadableByteStreamControllerRespondInClosedState(controller, firstDescriptor) {
1663-
assert(firstDescriptor.bytesFilled === 0);
1668+
assert(firstDescriptor.bytesFilled % firstDescriptor.elementSize === 0);
16641669

16651670
if (firstDescriptor.readerType === 'none') {
16661671
ReadableByteStreamControllerShiftPendingPullInto(controller);
@@ -1686,7 +1691,9 @@ function ReadableByteStreamControllerRespondInReadableState(controller, bytesWri
16861691
return;
16871692
}
16881693

1689-
if (pullIntoDescriptor.bytesFilled < pullIntoDescriptor.elementSize) {
1694+
if (pullIntoDescriptor.bytesFilled < pullIntoDescriptor.minimumFill) {
1695+
// A descriptor for a read() request that is not yet filled up to its minimum length will stay at the head
1696+
// of the queue, so the underlying source can keep filling it.
16901697
return;
16911698
}
16921699

0 commit comments

Comments
 (0)