W3C home > Mailing lists > Public > whatwg@whatwg.org > July 2014

Re: [whatwg] Questions about the Fetch API

From: 陈智昌 <willchan@chromium.org>
Date: Wed, 16 Jul 2014 09:51:59 -0700
Message-ID: <CAA4WUYhgS-OSV3emOHOu5tT8MWU75YGdwvWWVkMv17XNzQ1fdQ@mail.gmail.com>
To: Domenic Denicola <domenic@domenicdenicola.com>
Cc: "whatwg@lists.whatwg.org" <whatwg@lists.whatwg.org>, "Tab Atkins Jr." <jackalmage@gmail.com>, Juan Ignacio Dopazo <jdopazo@yahoo-inc.com>
On Tue, Jul 15, 2014 at 2:06 PM, Domenic Denicola <
domenic@domenicdenicola.com> wrote:

> From: willchan@google.com <willchan@google.com> on behalf of William Chan
> (陈智昌) <willchan@chromium.org>
>
> > Can you explain this in more detail? AFAICT, the fundamental difference
> we're talking about here is push vs pull sources. Option 1 is a push model,
> where fetch() creates a writable stream that has an underlying sink
> (Domenic, did I  get that right? I assume you meant sink and not source) of
> the request body. And the user code writes to the stream to stream output
> to the request body. In contrast, option 2 is a pull model, where fetch()
> accepts as input a readable stream and pulls from  it as the network is
> ready to write the request body out.
>
> This is interesting. I am not sure it is correct... At least, I don't
> think it is from a theoretical streams-model perspective. But whether that
> theory applies is an important question I hope you can help with.
>
> The way Node-esque streams model these things, is that readable streams
> wrap push vs. pull sources. Writable streams don't have such a distinction:
> you write to them as data becomes available. The interesting part comes
> when you pipe a readable stream to a writable stream. The pipe API makes it
> possible to either pull from the readable stream's underlying source as
> soon as the writable stream is ready to accept data, or to feed pushed data
> into the writable stream as it is pushed, subject to writable backpressure.


> So in both cases 1 and 2, if you have a readable stream and want to pipe
> it to the request body, it doesn't matter whether the readable stream is
> wrapping a push or a pull source: both will be handled by waiting for the
> writable stream to signal readiness, then giving the writable stream as
> much data as is ready: either by pushing, or by pulling.
>
> Does this match up well with your thoughts on push vs. pull and clients
> from later in your message? Or is my model pretty far off?
>

I think we are talking about different things. You are talking about
whether or not the source that is wrapped by a readable stream is a push or
pull source. I'm talking about the sink. The user agent can either say "OK,
now's a good time to write to the writable stream sink given to you by
fetch()" or it can say "OK, now's a good time for me to read from the
readable stream passed into fetch()". When I'm talking about push vs pull,
I'm saying that the web app writing into the writable stream provided by
fetch() is a push model, since the web app is pushing data down into the
sink (the user agent) until the user agent tells it to shut up
(backpressure). And the user agent reading from a readable stream provided
by the web app via fetch() is a pull model, since the user agent is pulling
data from the web app as needed.


> Looking at it from another direction, the purpose of the writable stream
> abstraction is to wrap resources like a file opened with the write flag, or
> a HTTP request body, into an interface people can both write chunks to, and
> pipe readable streams to. There is another possibility opposed to piping,
> though: just having functions that accept readable streams and read from
> them as necessary. Do you think this latter idea is inherently better than
> the piping idea?
>

Overall, yes. Although I do see your point about the API simplicity and
need to think more about it. I think it's solvable.


>
> The advantage of the piping idea is that it allows you to reuse the same
> code for piping another stream as for writing a chunk directly: that is,
> you can write code like [1], encapsulating all the interfacing with the
> underlying sink via a couple functions, and then you get an interface for
> both piping and chunk-writing to the resulting writable stream. Whereas,
> with the readable-stream-accepting-function interface, you would not have
> chunk-writing at all, and would have to develop a separate interface for
> that.
>
> I'd be really interested to hear if you think that the piping model is
> inherently worse than the accepting-function model. That would definitely
> make things interesting. My intuition is that it superficially looks
> different, but in reality it works the same after you go through a bit of
> indirection.
>

API-wise it's different, and I do see the natural ease of
piping/chunk-writing. But as I said earlier, it can be worse for
performance. Fundamentally, my point is that we want to delay writing as
much as possible, because it's better to keep data at the higher layer (the
web app) where the application has more information about how to schedule
data, rather then flushing it sooner to the system (the user agent in the
browsing case) if the system is just going to buffer the data. This is why
I'd rather the system only read data from the higher layers when it knows
it's going to flush everything out to the system sink (the network or a
file or whatever) rather than have it sit in buffers in the system. If data
needs to be buffered because the sink is too slow, it's better to buffer at
the higher layer than the lower layer. The reasons are numerous. (1) Better
prioritization. The higher layer can reorder writes. (2) Better coalescing.
If each written chunk results in a cost, like a checksum appended to the
end of the chunk, then writing fewer but larger chunks is better. So if you
buffer in the higher layer (the web app), you can coalesce chunks, rather
then immediately flushing them to the lower layer which doesn't know the
higher layer's protocol allows for coalescing since the user agent just
treats fetch()'s request body as an opaque byte stream (3) Better
compression. If you're compressing each chunk individually, then splitting
the data into fewer but larger chunks results in better compression. This
is why combining images into a larger image sprite gets better compression
than compressing each image individually. I can come up with many more
examples, but fundamentally, the idea is that it's better to keep the data
in the higher layer as much as possible. Do not push data down to the lower
layer as fast as you can. Let the lower layer pull data from the higher
layer only as needed. Presumably it'll pull data only as fast as needed to
make sure throughput doesn't drop.


> P.S.: I haven't made time to read your blog posts yet; sorry. If you feel
> this conversation would go a lot smoother if I did so then let me know and
> I can refrain from replying until then :)
>
> [1]: https://whatwg.github.io/streams/#ws-intro
>
Received on Wednesday, 16 July 2014 16:52:26 UTC

This archive was generated by hypermail 2.4.0 : Wednesday, 22 January 2020 17:00:21 UTC