- Notifications
You must be signed in to change notification settings - Fork23k
Editorial review: Document WebSocketStream#35548
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Conversation
github-actionsbot commentedAug 22, 2024 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
tomayac left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
This is in a really good shape. Just some nits.
Maybe ping@ricea for a spec editor review.
files/en-us/web/api/websockets_api/using_websocketstream/index.md OutdatedShow resolvedHide resolved
Uh oh!
There was an error while loading.Please reload this page.
files/en-us/web/api/websockets_api/using_websocketstream/index.md OutdatedShow resolvedHide resolved
Uh oh!
There was an error while loading.Please reload this page.
files/en-us/web/api/websockets_api/using_websocketstream/index.md OutdatedShow resolvedHide resolved
Uh oh!
There was an error while loading.Please reload this page.
files/en-us/web/api/websockets_api/using_websocketstream/index.md OutdatedShow resolvedHide resolved
Uh oh!
There was an error while loading.Please reload this page.
files/en-us/web/api/websockets_api/using_websocketstream/index.md OutdatedShow resolvedHide resolved
Uh oh!
There was an error while loading.Please reload this page.
files/en-us/web/api/websockets_api/using_websocketstream/index.md OutdatedShow resolvedHide resolved
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Co-authored-by: Thomas Steiner <tomac@google.com>
Co-authored-by: Thomas Steiner <tomac@google.com>
Co-authored-by: Thomas Steiner <tomac@google.com>
Co-authored-by: Thomas Steiner <tomac@google.com>
Co-authored-by: Thomas Steiner <tomac@google.com>
Co-authored-by: Thomas Steiner <tomac@google.com>
Co-authored-by: Thomas Steiner <tomac@google.com>
Co-authored-by: Thomas Steiner <tomac@google.com>
Co-authored-by: Thomas Steiner <tomac@google.com>
Co-authored-by: Thomas Steiner <tomac@google.com>
chrisdavidmills commentedAug 28, 2024
Thanks,@tomayac! I accepted all of your suggestions. I also checked and updated a few example snippets to use Also, because we need to specify I'm going to put this forward for editorial review now, but@ricea, feel free to have a look and make comments if you wish. It should take a little while for someone to pick it up, so there is time. |
wbamberg left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Here are comments on the non-reference pages.
| ###WebSocketStream | ||
| The {{domxref("WebSocketStream")}} API is a modern reimagining of WebSocket client-side JavaScript functionality. It is promise-based, and therefore simpler to work with than the traditional {{domxref("WebSocket")}} API in the modern JavaScript scosystem. In addition, it uses the[Streams API](/en-US/docs/Web/API/Streams_API) to handle receiving and sending messages, meaning that socket connections can take advantage of stream[backpressure](/en-US/docs/Web/API/Streams_API/Concepts#backpressure) automatically, regulating the speed of reading or writing to avoid bottlenecks in the application. See[Using WebSocketStream to write a client](/en-US/docs/Web/API/WebSockets_API/Using_WebSocketStream) for more information. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I think this needs more context. We have an intro about what websockets are, then go right into:
TheWebSocketStream API is a modern reimagining of WebSocket client-side JavaScript functionality. It is promise-based, and therefore simpler to work with than the traditionalWebSocket API ...
...but we haven't even talked about "traditional" WebSocket yet.
Also, "a modern reimagining of WebSocket client-side JavaScript functionality" reads like marketing to me. As a web developer who doesn't know yet about WebSockets, let alone Web Transport, what am I to make of all this?
IIUC there's a major drawback to WebSocketStream: it's nonstandard and only supported in one browser engine. Given that, I'm not sure MDN should recommend it above standard and cross-platform alternatives.
It would be very helpful here to lay out all the considerations, including Web Transport, in a neutral and factual way, to help developers make good choices.
It seems like the main motivation for WebSocketStream over WebSocket is to support backpressure, and I would put that first, before the "promise-based API" motivation (I doubt that would be a strong enough motivation alone for web developers to want to adopt this API). Theexplainer does a nice job of that actually.
So we might say something roughly like:
In the WebSocket API, there are two alternative ways to create and use web sockets: the
WebSocketinterface and theWebSocketStreaminterface.
- The
WebSocketinterface is stable and has good browser and server support. However it doesn't support backpressure (explain what this is and why it is a problem)- The
WebSocketStreamis an alternative toWebSocket, that's based on the Streams API and therefore does support backpressure. However, it's nonstandard and has poor cross-browser support.Additionally, the WebTransport API is expected to replace the WebSocket API for many applications, and supports backpressure along with many other features not supported by either
WebSocketorWebSocketStream.something something about when and in which circs people should choose WebSocket over WebTransport or vice versa
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Good call. In my next commit, I have used your suggested structure as a basis, and filled in the missing details.
| {{DefaultAPISidebar("WebSockets API")}}{{non-standard_header}} | ||
| The {{domxref("WebSocketStream")}} API is a modern reimagining of WebSocket client-side JavaScript functionality. It is promise-based, and therefore simpler to work with than the traditional {{domxref("WebSocket")}} API in the modern JavaScript scosystem. In addition, it uses the[Streams API](/en-US/docs/Web/API/Streams_API) to handle receiving and sending messages, meaning that socket connections can take advantage of stream[backpressure](/en-US/docs/Web/API/Streams_API/Concepts#backpressure) automatically, regulating the speed of reading or writing to avoid bottlenecks in the application. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
This is a copy/paste from the overview page. I'm not sure it's needed here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
The overview page no longer contains this paragraph. I have kept it on this page, but updated it to make it more concise and less marketing-y.
| The {{domxref("WebSocketStream")}} API is a modern reimagining of WebSocket client-side JavaScript functionality. It is promise-based, and therefore simpler to work with than the traditional {{domxref("WebSocket")}} API in the modern JavaScript scosystem. In addition, it uses the[Streams API](/en-US/docs/Web/API/Streams_API) to handle receiving and sending messages, meaning that socket connections can take advantage of stream[backpressure](/en-US/docs/Web/API/Streams_API/Concepts#backpressure) automatically, regulating the speed of reading or writing to avoid bottlenecks in the application. | ||
| This article explains how to use the {{domxref("WebSocketStream")}} API to create a simple WebSocket client. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
| This article explains how to use the {{domxref("WebSocketStream")}} API to create asimpleWebSocket client. | |
| This article explains how to use the {{domxref("WebSocketStream")}} API to create a WebSocket client. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Argh, the curse of "simple"! Removed in next commit ;-)
| >[!NOTE] | ||
| >Backpressure is an issue with the traditional`WebSocket` API — it has no way to apply backpressure, therefore when messages arrive faster than the application can process them, the application will either fill up the device's memory by buffering those messages, become unresponsive due to 100% CPU usage, or both. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Also, this note seems out of place, especially if you remove or cut down the first para. What would be helpful, IMO, would be something about what (if anything) the developer has to do to take advantage of backpressure support. I think nothing? i.e. if they just don't read, then backpressure is exerted. Is that right though?
The page athttps://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Concepts#backpressure says:
To use backpressure in aReadableStream, we can ask the controller for the chunk size desired by the consumer by querying theReadableStreamDefaultController.desiredSize property on the controller. If it is too low, our ReadableStream can tell its underlying source to stop sending data, and we backpressure along the stream chain.
...which I don't really understand - who is "we" here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I have removed the note from here, and put a shortened version on theWebSocket page.
In terms of how to use it, it is just automatic forWebSocketStream. I do say "automatically" in the opening paragraph. Do you think I need to be a bit more explicit?
It is somewhat out of scope for this PR to start updating the Streams API docs. We could do that in a separate PR. Maybe file an issue for now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
In terms of how to use it, it is just automatic for WebSocketStream. I do say "automatically" in the opening paragraph. Do you think I need to be a bit more explicit?
Yes, I do. The doc links tohttps://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Concepts#backpressure, which as I say is hard to understand, but does not make it obvious that the developer has to do nothing for backpressure to work.
I think it would be best to spell this out in the bit about "To read data from the socket, you can continuously callReadableStreamDefaultReader.read() ...", and will leave a suggestion there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
OK, cool. I've also added a little qualifier here in parens - "(no additional action required by the developer)" - which will hopefully help too.
| To write data to the socket, you can use {{domxref("WritableStreamDefaultWriter.write()")}}: | ||
| ```js | ||
| awaitwriter.write("My message"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Why await here?
chrisdavidmillsSep 6, 2024 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I was just including it becausewrite() returns a promise. I guess I don't need it for this trivial line. Removed.
| wss.close({ | ||
| code:1000, | ||
| reason:"That's all folks", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
We say a few times that it's preferable to useabort(), maybe better to show that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
We also say that close() is used when providing a custom code and/or reason. In this case we are providing a custom reason, so I think this is OK.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
To be pedantic, which I always am, it says "custom codeand reason".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
OK, all relevant instances now changed to "and/or".
| <h2>WebSocketStream Test</h2> | ||
| <p>Sends a ping every five seconds</p> | ||
| <divid="output"></div> | ||
| <script> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I think it would be easier to read this if the JS were in an external script.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Possibly so, but this would also make it slightly more annoying for a user to copy and paste and chuck into a file for testing purposes. I think it is better to leave as-is, by this token.
| slug:Web/API/WebSockets_API/Using_WebSocketStream | ||
| page-type:guide | ||
| status: | ||
| -non-standard |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
also experimental?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
OK, makes sense. I've also added it to theWebSocketStream interface pages.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
FWIW this should get added automatically from the BCD. But only in reference pages, not in guide pages.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Ah, OK. I keep forgetting what is automatically added and what isn't.
| The main bulk of our code is contained within the`start()` function, which we define and then immediately invoke. We await the {{domxref("WebSocketStream.opened", "opened")}} promise, then after it fulfills write a message to let the reader know the connection is successful and create {{domxref("ReadableStreamDefaultReader")}} and {{domxref("WritableStreamDefaultWriter")}} instances from the returned`readable` and`writable` properties. | ||
| Next, we`write()` a`"ping"` value to the socket and communicate that to the user. At this point, the server will respond with a`"pong"` message. We await the`read()` of the read message, communicate it to the user, then write another`"ping"` to the server after a timeout of 5 seconds. This continues the`"ping"`/`"pong"` loop indefinitely. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
| Next, we`write()` a`"ping"` value to the socket and communicate that to the user. At this point, the server will respond with a`"pong"` message. We await the`read()` of theread message, communicate it to the user, then write another`"ping"` to the server after a timeout of 5 seconds. This continues the`"ping"`/`"pong"` loop indefinitely. | |
| Next, we`write()` a`"ping"` value to the socket and communicate that to the user. At this point, the server will respond with a`"pong"` message. We await the`read()` of thereasponse, communicate it to the user, then write another`"ping"` to the server after a timeout of 5 seconds. This continues the`"ping"`/`"pong"` loop indefinitely. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
updated
| >[!NOTE] | ||
| >To get the example working, you'll also need a server component. We wrote our client to work along with the Deno server explained in[Writing a WebSocket server in JavaScript (Deno)](/en-US/docs/Web/API/WebSockets_API/Writing_a_WebSocket_server_in_JavaScript_Deno), but any compatible server will do. | ||
| First we grab a reference to a DOM element into which we will write output messages, and define a utility function that writes a message to it: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I'm a bit worried that this duplication of the code will make it hard to maintain. I understand though that just quoting the whole block then talking through it all might be indigestible, so idk what is for the best.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I appreciate that, but I've not got a better solution. Leaving as-is.
wbamberg left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Comments for the other pages.
| -`options` {{optional_inline}} | ||
| - : An object than can contain the following properties: | ||
| -`protocols` {{optional_inline}} | ||
| - : An array of strings representing custom protocols to use for the connection. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Should we explain what this option is used for? Also, are there syntactic restrictions on the strings?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Added. Info on allowable names was hard to track down, and I didn't really find anything concrete about syntactic restrictions. I have added some more useful info.
| >[!NOTE] | ||
| >Alternatively, you can use the {{domxref("WebSocketStream.close()")}} method to close a connection, however this is mainly needed if you wish to specify a custom code and reason for the server to report. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Don't think this should be a note. It works fine as a normal paragraph.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Agreed. I've de-noted it.
| -`url` | ||
| - : A string representing the URL of the WebSocket server you want to connect to with this`WebSocketStream` instance. Allowed URL schemes are`"ws"`,`"wss"`,`"http"`, and`"https"`. | ||
| -`options` {{optional_inline}} | ||
| - : An object than can contain the following properties: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
| - : An objectthan can contain the following properties: | |
| - : An objectthat can contain the following properties: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
fixed - ta!
| ##Syntax | ||
| ```js-nolint | ||
| new WebSocketStream(url, options) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
| new WebSocketStream(url, options) | |
| new WebSocketStream(url) | |
| new WebSocketStream(url, options) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Good call; updated.
| >[!NOTE] | ||
| >An alternative mechanism for closing a`WebSocketStream` is to specify an {{domxref("AbortSignal")}} in the[`signal`](/en-US/docs/Web/API/WebSocketStream/WebSocketStream#signal) option of the constructor upon creation. The associated {{domxref("AbortController")}} can then be used to close the WebSocket connection. This is generally the preferred mechanism, however`close()` can be used if you wish to specify a custom code and reason for the server to report. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Again I don't think this is a note.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Again, de-noted.
| - {{domxref("WebSocketStream.url", "url")}} {{ReadOnlyInline}} | ||
| - : Returns the URL of the WebSocket server that the`WebSocketStream` instance was created with. | ||
| - {{domxref("WebSocketStream.closed", "closed")}} {{ReadOnlyInline}} | ||
| - : Returns a {{jsxref("Promise")}} that fulfills with an object once the socket connection is closed. The object contains the closing code and reason sent by the server. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
re "sent by the server", same comment as in theclosed page, what if the client closes it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Same answer as above. I'm not sure if this needs an update. I have modified it slightly, to "as sent by the server"
| - {{domxref("WebSocketStream.closed", "closed")}} {{ReadOnlyInline}} | ||
| - : Returns a {{jsxref("Promise")}} that fulfills with an object once the socket connection is closed. The object contains the closing code and reason sent by the server. | ||
| - {{domxref("WebSocketStream.opened", "opened")}} {{ReadOnlyInline}} | ||
| - : Returns a {{jsxref("Promise")}} that fulfills with an object once the socket connection is successfully opened. The object contains several useful features, including a {{domxref("ReadableStream")}} and a {{domxref("WritableStream")}} instance for receiving and sending data on the connection. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
| - : Returns a {{jsxref("Promise")}} that fulfills with an object once the socket connection is successfully opened.The object contains several useful features, including a {{domxref("ReadableStream")}} and a {{domxref("WritableStream")}} instance for receiving and sending data on the connection. | |
| - : Returns a {{jsxref("Promise")}} that fulfills with an object once the socket connection is successfully opened.Among other properties, the object includes a {{domxref("ReadableStream")}} and a {{domxref("WritableStream")}} instance for receiving and sending data on the connection. |
Very picky. "several useful features" seems a bit odd. Should it be taken for granted that the properties are useful?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Yup, very picky. You are right too, which is even more annoying ;-)
I have updated the wording to "Among other features, this object contains a...", as the items listed aren't the actual property names, but the values they contain.
| {{APIRef("WebSockets API")}}{{non-standard_header}} | ||
| The**`opened`** read-only property of the | ||
| {{domxref("WebSocketStream")}} interface returns a {{jsxref("Promise")}} that fulfills with an object once the socket connection is successfully opened. The object contains several useful features, including a {{domxref("ReadableStream")}} and a {{domxref("WritableStream")}} instance for receiving and sending data on the connection. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
"several useful features" again.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Same change made here.
| -`extensions` | ||
| - : A string representing any extensions applied to the`WebSocketStream`. Such extensions are not currently defined, but may be in the future. Currently returns an empty string. | ||
| -`protocol` | ||
| - : A string representing any custom protocol used to open the current WebSocket connection, as specified in the[`protocols`](/en-US/docs/Web/API/WebSocketStream/WebSocketStream#protocols) option of the`WebSocketStream()` constructor. Returns an empty string if no custom protocol was used. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I guess this must be one of the ones given in the constructor?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I don't really understand the question. The text clearly says "as specified in theprotocols option of theWebSocketStream() constructor".
I've updated the end of the description to "if no custom protocol was set during instantiation", in case it is useful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I don't think "as specified in..." is very clear.protocols is an array, this is a single item. I'm assuming what happens is that the client passes, inprotocols, an array of custom protocols it is prepared to work with, and the server responds with a single protocol from the array. But the docs don't say this, so I have to guess.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Ah, OK, I getcha now. I've done a bit of reading around this, and have updated this description to:
A string representing the sub-protocol used to open the current WebSocket connection (chosen from the options specified in the
protocolsoption of theWebSocketStream()constructor). Returns an empty string if no sub-protocol has been used to open the connection (i.e. no sub-protocol options were included in the constructor call).
| ##Examples | ||
| ```js | ||
| constoutput=document.querySelector("#output"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
As for closed, I think a smaller more focused example would be better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I've cut it down quite a bit, but not as much as theclosed example.
| {{DefaultAPISidebar("WebSockets API")}} | ||
| The**WebSocket API** is an advanced technology that makes it possible to open a two-way interactive communication session between the user's browser and a server. With this API, you can send messages to a server and receive event-driven responses without having to poll the server for a reply. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
| The**WebSocket API**is an advanced technology thatmakes it possible to open a two-way interactive communication session between the user's browser and a server. With this API, you can send messages to a server and receive event-driven responses without having to poll the server for a reply. | |
| The**WebSocket API** makes it possible to open a two-way interactive communication session between the user's browser and a server. With this API, you can send messages to a server and receive responses without having to poll the server for a reply. |
(not necessarily event-driven)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Sounds reasonable. Updated to use your wording.
| - The`WebSocketStream` is a {{jsxref("Promise")}}-based alternative to`WebSocket`. It uses the[Streams API](/en-US/docs/Web/API/Streams_API) to handle receiving and sending messages, meaning that socket connections can take advantage of stream backpressure automatically, regulating the speed of reading or writing to avoid bottlenecks in the application. However,`WebSocketSteam` is non-standard and currently only supported in one rendering engine. | ||
| Additionally, the[WebTransport API](/en-US/docs/Web/API/WebTransport_API) is expected to replace the WebSocket API for many applications. WebTransport is a versatile, low-level API that provides backpressure and many other features not supported by either`WebSocket` or`WebSocketStream`, such as unidirectional streams, out-of-order delivery, and unreliable data transmission via datagrams. WebTransport is more complex to use than WebSockets and its cross-browser support is not as wide, but it enables the implementation of sophisticated solutions. If standard WebSocket connections are a good fit for your use case and you need wide browser compatibility, you should employ the WebSockets API to get up and running quickly. However, if your application requires a non-standard custom solution, then you should use the WebTransport API. | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Thank you, I like this a lot better.
| >[!NOTE] | ||
| >To get the example working, you'll also need a server component. We wrote our client to work along with the Deno server explained in[Writing a WebSocket server in JavaScript (Deno)](/en-US/docs/Web/API/WebSockets_API/Writing_a_WebSocket_server_in_JavaScript_Deno), but any compatible server will do. | ||
| The HTML for the demo is as follows. It includes informational {{htmlelment("h1")}} and {{htmlelment("h1")}} elements, a {{htmlelment("button")}} to close the WebSocket connection that is initially disabled, and a {{htmlelment("div")}} for us to write output messages into. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
| The HTML for the demo is as follows. It includes informational {{htmlelment("h1")}} and {{htmlelment("h1")}} elements, a {{htmlelment("button")}} to close the WebSocket connection that is initially disabled, and a {{htmlelment("div")}} for us to write output messages into. | |
| The HTML for the demo is as follows. It includes informational {{htmlelement("Heading_Elements", "h2")}} and {{htmlelement("p")}} elements, a {{htmlelement("button")}} to close the WebSocket connection that is initially disabled, and a {{htmlelement("div")}} for us to write output messages into. |
(please double check the syntax for the H2)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Oh darn, thanks for the fixes there.
The syntax worked, but I ended up replacing the h2 macro call with a regular link, as for some reason the link was not showing the angle brackets or in code font.
| >[!NOTE] | ||
| >Backpressure is an issue with the traditional`WebSocket` API — it has no way to apply backpressure, therefore when messages arrive faster than the application can process them, the application will either fill up the device's memory by buffering those messages, become unresponsive due to 100% CPU usage, or both. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
In terms of how to use it, it is just automatic for WebSocketStream. I do say "automatically" in the opening paragraph. Do you think I need to be a bit more explicit?
Yes, I do. The doc links tohttps://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Concepts#backpressure, which as I say is hard to understand, but does not make it obvious that the developer has to do nothing for backpressure to work.
I think it would be best to spell this out in the bit about "To read data from the socket, you can continuously callReadableStreamDefaultReader.read() ...", and will leave a suggestion there.
| To read data from the socket, you can continuously call {{domxref("ReadableStreamDefaultReader.read()")}} until the stream has finished, which is indicated by`done` being`true`: | ||
| ```js | ||
| while (true) { | ||
| const {value,done }=awaitreader.read(); | ||
| if (done) { | ||
| break; | ||
| } | ||
| // Process value in some way | ||
| } | ||
| ``` | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Here I think is where we should spell out the backpressure thing. Perhaps something like:
| To read data from the socket, you can continuously call {{domxref("ReadableStreamDefaultReader.read()")}} until the stream has finished, which is indicated by`done` being`true`: | |
| ```js | |
| while (true) { | |
| const { value, done } = await reader.read(); | |
| if (done) { | |
| break; | |
| } | |
| // Process value in some way | |
| } | |
| ``` | |
| To read data from the socket, you can continuously call {{domxref("ReadableStreamDefaultReader.read()")}} until the stream has finished, which is indicated by`done` being`true`: | |
| ```js | |
| while (true) { | |
| const { value, done } = await reader.read(); | |
| if (done) { | |
| break; | |
| } | |
| // Process value in some way | |
| } | |
| ``` | |
| Note that the client can control the rate at which it receives data by calling`read()` only when it is ready: if data is arriving faster than the client can read it, the underlying Streams API automatically exerts backpressure on the sender. | |
(assuming that is true!)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I don't think the point whenread() is called has anything to do with backpressure. This is not automatic, but a choice made by the developer in the code?
I've read around this subject a bit, and added the following paragraph after the code snippet:
The browser automatically controls the rate at which the client receives and sends data by applying backpressure when needed. If data is arriving faster than the client can
read()it, the underlying Streams API exerts backpressure on the server. In addition,write()operations will only proceed if it is safe to do so.
| constreader=readable.getReader(); | ||
| constwriter=writable.getWriter(); | ||
| awaitwriter.write("ping"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
You don't have to await to make it work: await just ensures that the promise fulfills before it executes the next line.
I mean, usually I seeawait being used if you want to make your code behave like synchronous code, for instance if it's returning something you need in the next line:
constresponse=awaitfetch(url);constdata=awaitresponse.json();
(or as we are doing withread(), where we need the read value in the next line).
But in this case we're not, so what's functionally the difference between awaiting and not awaiting, except that we're imposing an ordering when we maybe don't need to.
I suppose awaiting means we can reportwriteToScreen("SENT: ping");, because we knowwrite() didn't reject, although it's not clear whether not rejecting actually means we sent it, ashttps://developer.mozilla.org/en-US/docs/Web/API/WritableStreamDefaultWriter/write says:
Note that what "success" means is up to the underlying sink; it might indicate that the chunk has been accepted, and not necessarily that it is safely saved to its ultimate destination.
But then if that is the reason, isn't it better to use try/catch so we can report an error?
So it could say:
try{awaitwriter.write("ping");writeToScreen("SENT: ping");}catch(e){writeToScreen(`Error writing to socket:${e.message}`);}
...and then there's a use forawait.
Ah, maybe it's not worth worrying about.
| slug:Web/API/WebSockets_API/Using_WebSocketStream | ||
| page-type:guide | ||
| status: | ||
| -non-standard |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
FWIW this should get added automatically from the BCD. But only in reference pages, not in guide pages.
| reason:"That's all folks", | ||
| }); | ||
| closeBtn.disabled=false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
true?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Good call; updated.
| wss.close({ | ||
| code:1000, | ||
| reason:"That's all folks", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
To be pedantic, which I always am, it says "custom codeand reason".
Uh oh!
There was an error while loading.Please reload this page.
wbamberg left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
👍 thanks Chris!
chrisdavidmills commentedSep 13, 2024
Likewise, thanks so much for the review Will! |
Description
Chrome 124 supports the
WebSocketStreamAPI (see the associatedChromeStatus entry).Note that
WebSocketStreamis currently supported in Chromium-based browsers, and is not yet part of a standard so I will mark it as non-standard for now. Seewhatwg/websockets#48 for progress on that.Motivation
Additional details
Related issues and pull requests