[streams] Add ReadableByteStream observing API (#362)

I and @yutakahirano discussed alternatives to the wrapper solution.

The wrapper solution requires composition/subclassing. We could instead introduce some interface to the ReadableByteStream.

#### Approach A: Receive a report object on every unlock

##### API and algorithm

```
class ReadableByteStream {
...
  registerObserver(observer);
  unregisterObserver(id);
...
}
```

```
class ReadableByteStreamObserver {
  report();
}
```

- Both `registerObserver()` and `unregisterObserver()` can be called only when the `ReadableByteStream` is not locked.
- Every time when a `ReadableByteStream` is unlocked, the `ReadableByteStream` calls `observer.report()` with `{bytesRead: <the number of bytes read during the lock>}`.
    - `reader.read()` and `byobReader.read(view)` automatically accumulates the number of bytes read into the `ReadableByteStream` so that it can be reported to the observer on unlocking
    - The default `pipeTo()` and any optimized `pipeTo()` would also reports the number of bytes read before unlocking the `ReadableByteStream`.
- `unregisterObserver(id)` takes the object returned by `registerObserver()` to unregister the observer.

##### `Request` object implementation

NOTE: `unregisterObserver()` is not used for implementing the Fetch API. Just for APIs to be proposed in the future.

Algorithm:

- on construction,
    - create an object to receive `.report()` call
    - call `registerObserver()`passing the object to get the ID object
- when `.report()` is called,
    - remember (accumulate) the number of bytes read
- when `cache.put()` is called,
    - [Plan A] fail if the number of bytes read is positive
    - [Plan B] fail if at least one `.report()` call was made

#### Approach B: Get a report object on registration, and look at the object when needed

##### API and algorithm

```
class ReadableByteStream {
...
  registerObserver();
  unregisterObserver(id);
...
}
```

- `stream.registerObserver()` returns an object which the stream updates on every unlock.

##### `Request` object implementation

- call `registerObserver()` to get the report object
- on `cache.put()` call, check the report object to see if any bytes have been read out

### Advantage to the wrapper solution

- In terms of knowing that at least one getReader() call happened, this observer solution doesn't have so much advantage to the wrapper solution.
- But this observer solution enables observing the number of bytes by requiring ReadableByteStream implementors to report some data specified in the spec e.g. the number of bytes. This is hard to realize by the wrapper solution. Especially regarding optimized one.

### Alternative: Using generic Event mechanism

Instead of introducing a dedicated mechanism, use `addEventListener`/`removeEventListener` given that if one doesn't know the function object, it cannot unregister the event listener

### Requirements analysis

Both approach A and B satisfie (1)-(4) of https://github.com/yutakahirano/fetch-with-streams/issues/37#issuecomment-107836439 but not (5) (7). I'd say (6) is also satisfied as there's such path-dependent data exists only when some observers are registered and is placed outside the stream object.

Re (4), custom `ReadableByteStream` is expected to implement these (`registerObserver` and `unregisterObserver()`) methods correctly. Whether (1) is satisfied or not depends on if they're implemented correctly or not.


---
Reply to this email directly or view it on GitHub:
https://github.com/whatwg/streams/issues/362

Received on Monday, 15 June 2015 12:19:57 UTC