-
Notifications
You must be signed in to change notification settings - Fork 22.5k
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Editorial review: Document WebSocketStream #35548
Conversation
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
Outdated
Show resolved
Hide resolved
files/en-us/web/api/websockets_api/using_websocketstream/index.md
Outdated
Show resolved
Hide resolved
files/en-us/web/api/websockets_api/using_websocketstream/index.md
Outdated
Show resolved
Hide resolved
files/en-us/web/api/websockets_api/using_websocketstream/index.md
Outdated
Show resolved
Hide resolved
files/en-us/web/api/websockets_api/using_websocketstream/index.md
Outdated
Show resolved
Hide resolved
files/en-us/web/api/websockets_api/using_websocketstream/index.md
Outdated
Show resolved
Hide resolved
Co-authored-by: Thomas Steiner <[email protected]>
Co-authored-by: Thomas Steiner <[email protected]>
Co-authored-by: Thomas Steiner <[email protected]>
Co-authored-by: Thomas Steiner <[email protected]>
Co-authored-by: Thomas Steiner <[email protected]>
Co-authored-by: Thomas Steiner <[email protected]>
Co-authored-by: Thomas Steiner <[email protected]>
Co-authored-by: Thomas Steiner <[email protected]>
Co-authored-by: Thomas Steiner <[email protected]>
Co-authored-by: Thomas Steiner <[email protected]>
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. |
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:
The 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 WebSocket 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). The explainer 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
WebSocket
interface and theWebSocketStream
interface.
- The
WebSocket
interface 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
WebSocketStream
is 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
WebSocket
orWebSocketStream
. 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 a simple WebSocket 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 at https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Concepts#backpressure says:
To use backpressure in a ReadableStream, we can ask the controller for the chunk size desired by the consumer by querying the ReadableStreamDefaultController.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 the WebSocket
page.
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?
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 to https://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 call ReadableStreamDefaultReader.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 | ||
await writer.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?
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 because write()
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 use abort()
, 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 code and 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> | ||
<div id="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 the WebSocketStream
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 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. | |
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 reasponse, 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.
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 object than can contain the following properties: | |
- : An object that 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 the closed
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 the protocols
option of the WebSocketStream()
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, in protocols
, 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
protocols
option 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 | ||
const output = 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 the closed
example.
browser-compat: api.WebSocket | ||
browser-compat: | ||
- api.WebSocket | ||
- api.WebSocketStream | ||
--- | ||
|
||
{{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 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. | |
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 to https://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 call ReadableStreamDefaultReader.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 } = await reader.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 when read()
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.
const reader = readable.getReader(); | ||
const writer = writable.getWriter(); | ||
|
||
await writer.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 see await
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:
const response = await fetch(url);
const data = await response.json();
(or as we are doing with read()
, 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 report writeToScreen("SENT: ping");
, because we know write()
didn't reject, although it's not clear whether not rejecting actually means we sent it, as https://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 {
await writer.write("ping");
writeToScreen("SENT: ping");
} catch(e) {
writeToScreen(`Error writing to socket: ${e.message}`);
}
...and then there's a use for await
.
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 code and 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.
👍 thanks Chris!
Likewise, thanks so much for the review Will! |
Description
Chrome 124 supports the
WebSocketStream
API (see the associated ChromeStatus entry).Note that
WebSocketStream
is currently supported in Chromium-based browsers, and is not yet part of a standard so I will mark it as non-standard for now. See whatwg/websockets#48 for progress on that.Motivation
Additional details
Related issues and pull requests