With web_socket_channel, why await channel.ready after WebSocketChannel.connect, and how does a failed connection show up in a Flutter app?
answer
- connect returns before the handshake
- ready completes or completes with error
- WebSocketChannelException wraps the cause
- stream emits the error, then done
- connectTimeout gives TimeoutException
basics
~20 sconnect returns before the handshake; channel.ready completes when the socket is open or with an error if it failed. A failure also reaches channel.stream as a WebSocketChannelException followed by done, so both must be handled.
solid answer
~40 s`WebSocketChannel.connect` only starts the connection. The `ready` future completes once the server has accepted the upgrade, and the package documents that it must complete before you send. If the host is unreachable, TLS fails or the server rejects the upgrade, `ready` completes with an error, a `WebSocketChannelException` wrapping the original cause, and the same error is added to `channel.stream`, which then closes. So I `await channel.ready` inside a `try`/`catch` to show an offline state or schedule a reconnect, and I give the stream subscription an `onError` and `onDone` as well. With `IOWebSocketChannel.connect(..., connectTimeout: ...)` a slow handshake fails with a `TimeoutException` instead. Skipping `ready` means a failed connection either goes unnoticed or surfaces as an unhandled error.
code
dart · 12 linesFuture<WebSocketChannel?> openScores(Uri uri) async {
final channel = WebSocketChannel.connect(uri);
try {
await channel.ready;
return channel;
} on WebSocketChannelException catch (e) {
debugPrint('scores socket failed: ${e.inner}');
return null;
} on TimeoutException {
return null;
}
}go deeper
Remember that connect returns before the socket is open and that you await channel.ready inside try/catch before sending.
Explain the two places a failure appears, ready and the stream, which exception types to expect in version 3, and why sending early loses messages.
Show how ready and onDone feed one connection-state model that drives both the UI and the reconnect loop.
Consider how connection failures are reported and measured across app versions so a backend outage is visible quickly rather than as silent client errors.
## Why connect cannot tell you much `WebSocketChannel.connect(uri)` is synchronous: it returns a `WebSocketChannel` immediately so you can wire up listeners, while the TCP connection, TLS and HTTP upgrade happen in the background. The channel cannot know yet whether any of that will succeed. Since web_socket_channel 2.3.0 the package exposes that outcome as **`channel.ready`**, a `Future<void>`, and its examples await it before sending anything. ## What happens on failure In version 3.x, `WebSocketChannel.connect` is implemented by an adapter over `package:web_socket`. When the underlying connect fails, the adapter does three things: 1. **Completes `ready` with an error.** The error is a `WebSocketChannelException` whose `inner` field holds the original exception; a `TimeoutException` is passed through unchanged for compatibility. 2. **Adds the same error to `channel.stream`.** 3. **Closes the stream**, so a listener's `onDone` fires right after its `onError`. | Situation | `ready` | `channel.stream` | |---|---|---| | handshake succeeds | completes normally | delivers messages | | host unreachable, TLS failure, upgrade refused | completes with `WebSocketChannelException` | error, then done | | `IOWebSocketChannel.connect` with `connectTimeout` exceeded | completes with `TimeoutException` | error, then done | | connected, later dropped | already completed | done, with `closeCode` set | The last row matters: `ready` describes only the **opening**. A connection that opens and later drops is reported through the stream ending, never through `ready` again. ## Handling both channels of information A robust client treats `ready` and the stream as two different signals: - **`await channel.ready` in a `try`/`catch`** decides between "connected" and "could not connect", which drives an offline indicator or the first reconnect attempt. - **`stream.listen(onData, onError: ..., onDone: ...)`** covers the whole life of the connection: messages, a late error, and the end. - If neither `ready` nor the stream has an error handler, the failure has nowhere to go and is reported as an **unhandled asynchronous error**: in Flutter it lands in the zone's error handler or the console instead of your UI. ## Sending too early The `ready` documentation states that it must complete before data is sent through the sink. Sending before that is not a supported use: if the connection then fails, the message is simply lost, and the app has no signal that it never left the device. Ordering the code as connect, `await ready`, subscribe, send removes the ambiguity. ## Testing the failure paths The failure behaviour is easy to pin down in a plain Dart test, which is worth doing because it is the code path developers exercise least: 1. Connect to a port where nothing listens, for example `ws://localhost:9`. 2. Assert that `channel.ready` completes with an error of type `WebSocketChannelException`. 3. Assert that `channel.stream` emits that error and then closes, using `package:test` matchers such as `emitsError` followed by `emitsDone`. 4. For the app's own layer, inject a fake channel whose `ready` fails and check that the repository publishes an offline state and schedules a retry. These tests also catch an upgrade of the package that changes exception types, which is exactly what happened between 2.x and 3.0. ## A note on versions In web_socket_channel 3.0 the `WebSocketChannel` constructor was removed, the class became an `abstract interface`, and `IOWebSocketChannel.ready` switched from throwing `WebSocketException` to `WebSocketChannelException`. Code or answers catching `SocketException` or `WebSocketException` from `ready` describe the 2.x behaviour; in 3.x, catch `WebSocketChannelException` (and `TimeoutException` if you set a connect timeout) and inspect `inner` for the cause. ## In the live-scores screen For a scores feed, the difference is visible to users: without `ready` handling the screen shows a frozen last score and no hint that the feed never connected; with it, the screen shows "reconnecting" and the reconnect loop starts immediately.
- Does channel.ready tell you when an open connection drops later?No. `ready` completes once, for the opening handshake. A later drop is reported by `channel.stream` finishing: the subscription's `onDone` runs, and `closeCode` and `closeReason` show what, if anything, the server sent. Reconnect logic has to watch the stream, not `ready`.
- How do you find the underlying cause inside a WebSocketChannelException?Read its `inner` field, which holds the original exception from the platform layer, such as a socket or TLS error; `message` is that exception's `toString()`. Log `inner` for diagnostics, and show users a generic offline state.
saying these in an interview costs you the question
- If connect returns without throwing, the socket is connected
- channel.ready fires again every time the connection drops
- A failed connect throws synchronously from WebSocketChannel.connect
- In version 3, ready fails with a raw SocketException you should catch
- The stream stays open after a connection failure, waiting for a retry