Re: [whatwg/streams] Early draft of WritableStream "Design Philosophy" section (#718)

domenic commented on this pull request.



> @@ -2568,6 +2568,48 @@ nothrow>ReadableByteStreamControllerShouldCallPull ( <var>controller</var> )</h4
   writeRandomBytesForever(myWritableStream).catch(e => console.error("Something broke", e));
   </code></pre>
 </div>
+
+<h3 id="ws-design-philosopy">Design Philosophy</h3>
+
+<div>While sharing the principles for streams in general, a number of additional principles have informed the design of
+the WritableStream class.</div>
+
+<ul>
+  <li><p>Only one sink method can ever be executing at a time.
+  <li><p>Sink methods are treated as atomic.

IMO this can be one paragraph instead of sub-bullets

> @@ -2568,6 +2568,48 @@ nothrow>ReadableByteStreamControllerShouldCallPull ( <var>controller</var> )</h4
   writeRandomBytesForever(myWritableStream).catch(e => console.error("Something broke", e));
   </code></pre>
 </div>
+
+<h3 id="ws-design-philosopy">Design Philosophy</h3>
+
+<div>While sharing the principles for streams in general, a number of additional principles have informed the design of
+the WritableStream class.</div>
+
+<ul>
+  <li><p>Only one sink method can ever be executing at a time.
+  <li><p>Sink methods are treated as atomic.
+    <ul>
+      <li><p>"Atomic" here means that the time between a sink method being called and the returned Promise resolving or
+        rejecting is treated as indivisible.

This doesn't really clarify. What would divide them? I think the next bullet points actually state what atomic means.

> @@ -2568,6 +2568,48 @@ nothrow>ReadableByteStreamControllerShouldCallPull ( <var>controller</var> )</h4
   writeRandomBytesForever(myWritableStream).catch(e => console.error("Something broke", e));
   </code></pre>
 </div>
+
+<h3 id="ws-design-philosopy">Design Philosophy</h3>
+
+<div>While sharing the principles for streams in general, a number of additional principles have informed the design of
+the WritableStream class.</div>
+
+<ul>
+  <li><p>Only one sink method can ever be executing at a time.
+  <li><p>Sink methods are treated as atomic.
+    <ul>
+      <li><p>"Atomic" here means that the time between a sink method being called and the returned Promise resolving or
+        rejecting is treated as indivisible.
+      <li><p>A new sink method will never be called until the Promise from the previous one has resolved.
+      <li><p>State changes do not take effect until any in-flight sink method has completed.

"State changes" isn't too clear here, especially given the next two bullet points worth of exceptions.

> +  <li><p>Only one sink method can ever be executing at a time.
+  <li><p>Sink methods are treated as atomic.
+    <ul>
+      <li><p>"Atomic" here means that the time between a sink method being called and the returned Promise resolving or
+        rejecting is treated as indivisible.
+      <li><p>A new sink method will never be called until the Promise from the previous one has resolved.
+      <li><p>State changes do not take effect until any in-flight sink method has completed.
+    </ul>
+  <li><p>Exception: If something has happened that will error the stream, for example writer.abort() has been called, then
+      new calls to writer.write() will start failing immediately. There's no user benefit in waiting for the current operation
+      to complete before informing the user that writer.write() has failed.
+  <li><p>The writer.ready promise and the value of writer.desiredSize reflect whether a write() performed right
+  now would be effective.
+      <ul>
+        <li>writer.ready and writer.desiredSize will change even in the middle of executing a sink method, as soon as we
+        know that calling writer.write() won't work any more.

They also change in cases besides "won't work" though

> +        rejecting is treated as indivisible.
+      <li><p>A new sink method will never be called until the Promise from the previous one has resolved.
+      <li><p>State changes do not take effect until any in-flight sink method has completed.
+    </ul>
+  <li><p>Exception: If something has happened that will error the stream, for example writer.abort() has been called, then
+      new calls to writer.write() will start failing immediately. There's no user benefit in waiting for the current operation
+      to complete before informing the user that writer.write() has failed.
+  <li><p>The writer.ready promise and the value of writer.desiredSize reflect whether a write() performed right
+  now would be effective.
+      <ul>
+        <li>writer.ready and writer.desiredSize will change even in the middle of executing a sink method, as soon as we
+        know that calling writer.write() won't work any more.
+          <div class=note>Because promises are dispatched asynchronously, the state can still change between
+          writer.ready becoming fulfilled and write() being called.</div>
+        <li>The value of writer.desiredSize decreases synchronously with every call to writer.write(). This implies that
+        strategy.size() is executed synchronously.

Given how we're not calling strategy.size as a method, I'd state this as "the queuing strategy's size() function is executed..."

> +      new calls to writer.write() will start failing immediately. There's no user benefit in waiting for the current operation
+      to complete before informing the user that writer.write() has failed.
+  <li><p>The writer.ready promise and the value of writer.desiredSize reflect whether a write() performed right
+  now would be effective.
+      <ul>
+        <li>writer.ready and writer.desiredSize will change even in the middle of executing a sink method, as soon as we
+        know that calling writer.write() won't work any more.
+          <div class=note>Because promises are dispatched asynchronously, the state can still change between
+          writer.ready becoming fulfilled and write() being called.</div>
+        <li>The value of writer.desiredSize decreases synchronously with every call to writer.write(). This implies that
+        strategy.size() is executed synchronously.
+       </ul>
+   <li><p>The writer.closed promise and the promises returned by writer.close() and writer.abort() do not resolve or
+   reject until no sink methods are executing and no further sink methods will be executing.
+       <ul>
+         <li>If the user of the WritableStream wants to retry against the same underlying data item, it is important to

"data item" = chunk?

"Retry against" is a bit confusing to me.

> +      <ul>
+        <li>writer.ready and writer.desiredSize will change even in the middle of executing a sink method, as soon as we
+        know that calling writer.write() won't work any more.
+          <div class=note>Because promises are dispatched asynchronously, the state can still change between
+          writer.ready becoming fulfilled and write() being called.</div>
+        <li>The value of writer.desiredSize decreases synchronously with every call to writer.write(). This implies that
+        strategy.size() is executed synchronously.
+       </ul>
+   <li><p>The writer.closed promise and the promises returned by writer.close() and writer.abort() do not resolve or
+   reject until no sink methods are executing and no further sink methods will be executing.
+       <ul>
+         <li>If the user of the WritableStream wants to retry against the same underlying data item, it is important to
+         have confidence that all other operations have ceased.
+         <li>This principle also applies to the ReadableStream pipeTo() method.
+       </ul>
+    <li><p>Promises resolve or reject in consistent order. In particular, writer.ready always resolves before

I think "fulfill" is more informative here (although both are technically accurate)

> +        <li>writer.ready and writer.desiredSize will change even in the middle of executing a sink method, as soon as we
+        know that calling writer.write() won't work any more.
+          <div class=note>Because promises are dispatched asynchronously, the state can still change between
+          writer.ready becoming fulfilled and write() being called.</div>
+        <li>The value of writer.desiredSize decreases synchronously with every call to writer.write(). This implies that
+        strategy.size() is executed synchronously.
+       </ul>
+   <li><p>The writer.closed promise and the promises returned by writer.close() and writer.abort() do not resolve or
+   reject until no sink methods are executing and no further sink methods will be executing.
+       <ul>
+         <li>If the user of the WritableStream wants to retry against the same underlying data item, it is important to
+         have confidence that all other operations have ceased.
+         <li>This principle also applies to the ReadableStream pipeTo() method.
+       </ul>
+    <li><p>Promises resolve or reject in consistent order. In particular, writer.ready always resolves before
+    writer.closed, even in cases where they both resolve "at the same time".

I'd state this as "where both are fulfilling in reaction to the same occurrence"

> +          writer.ready becoming fulfilled and write() being called.</div>
+        <li>The value of writer.desiredSize decreases synchronously with every call to writer.write(). This implies that
+        strategy.size() is executed synchronously.
+       </ul>
+   <li><p>The writer.closed promise and the promises returned by writer.close() and writer.abort() do not resolve or
+   reject until no sink methods are executing and no further sink methods will be executing.
+       <ul>
+         <li>If the user of the WritableStream wants to retry against the same underlying data item, it is important to
+         have confidence that all other operations have ceased.
+         <li>This principle also applies to the ReadableStream pipeTo() method.
+       </ul>
+    <li><p>Promises resolve or reject in consistent order. In particular, writer.ready always resolves before
+    writer.closed, even in cases where they both resolve "at the same time".
+</ul>
+
+<div>Some of these design decisions improve predictability, ease-of-use and safety for developers at the expense of

Oxford comma please (after "ease-of-use")

I know it's not markup-critiquing time yet but this `<div>` seems pretty unnecessary :)

> +          writer.ready becoming fulfilled and write() being called.</div>
+        <li>The value of writer.desiredSize decreases synchronously with every call to writer.write(). This implies that
+        strategy.size() is executed synchronously.
+       </ul>
+   <li><p>The writer.closed promise and the promises returned by writer.close() and writer.abort() do not resolve or
+   reject until no sink methods are executing and no further sink methods will be executing.
+       <ul>
+         <li>If the user of the WritableStream wants to retry against the same underlying data item, it is important to
+         have confidence that all other operations have ceased.
+         <li>This principle also applies to the ReadableStream pipeTo() method.
+       </ul>
+    <li><p>Promises resolve or reject in consistent order. In particular, writer.ready always resolves before
+    writer.closed, even in cases where they both resolve "at the same time".
+</ul>
+
+<div>Some of these design decisions improve predictability, ease-of-use and safety for developers at the expense of

I think this could move up to merge with the intro actually.

> @@ -2568,6 +2568,48 @@ nothrow>ReadableByteStreamControllerShouldCallPull ( <var>controller</var> )</h4
   writeRandomBytesForever(myWritableStream).catch(e => console.error("Something broke", e));
   </code></pre>
 </div>
+
+<h3 id="ws-design-philosopy">Design Philosophy</h3>
+
+<div>While sharing the principles for streams in general, a number of additional principles have informed the design of
+the WritableStream class.</div>

I wonder if we can make clearer the "scope" of this section. It's got great info but it's of a very specific kind. It's not about the philosophy behind writable streams in general (of the type of stuff covered by the "Model" section). It's more about the details of its API surface and the interactions between the sink, controller, and writer APIs. Not sure on the exact phrasing, but that might be a start.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/whatwg/streams/pull/718#pullrequestreview-29895885

Received on Thursday, 30 March 2017 04:12:52 UTC