Why can't i catch this TimeoutException

Issue

I am currently building a Stomp WebSocket connection for Web where i want to control the connection timeout value. This is my connect method:

Future<WebSocketChannel> connect(StompConfig config) {
  final completer = Completer<HtmlWebSocketChannel>();
  final webSocket = WebSocket(config.url)..binaryType = BinaryType.list.value;
  var onOpenEvent = webSocket.onOpen.first;
  if (config.connectionTimeout.inMilliseconds > 0) {
    onOpenEvent = onOpenEvent.timeout(config.connectionTimeout);
  }
  onOpenEvent.then((value) {
    completer.complete(HtmlWebSocketChannel(webSocket));
  });
  webSocket.onError.first.then((err) {
    completer.completeError(WebSocketChannelException.from(err));
  });

  return completer.future;
}

and this is how i am calling it:

try {
    _channel = await platform.connect(config);
} catch (err) {
   print('Caught error');
}

Sadly i seem to be unable to catch the TimeoutException thrown by the onOpenEvent.timeout(..). Instead it just prints this (seemingly uncaught exception):

Error: TimeoutException after 0:00:05.000000: Future not completed
    at Object.createErrorWithStack (http://localhost:59275/dart_sdk.js:5044:12)
    at Object._rethrow (http://localhost:59275/dart_sdk.js:37476:16)
    at async._AsyncCallbackEntry.new.callback (http://localhost:59275/dart_sdk.js:37472:13)
    at Object._microtaskLoop (http://localhost:59275/dart_sdk.js:37332:13)
    at _startMicrotaskLoop (http://localhost:59275/dart_sdk.js:37338:13)
    at http://localhost:59275/dart_sdk.js:33109:9

What am i doing wrong? My previous code looked like this and worked for TimeoutException, but sadly not for any other exception thrown by the WebSocket:

Future<WebSocketChannel> connect(StompConfig config) async {
  final webSocket = WebSocket(config.url)..binaryType = BinaryType.list.value;
  var onOpenEvent = webSocket.onOpen.first;
  if (config.connectionTimeout.inMilliseconds > 0) {
    onOpenEvent = onOpenEvent.timeout(config.connectionTimeout);
  }
  await onOpenEvent;
  return HtmlWebSocketChannel(webSocket);
}

Thanks for any suggestions!

Solution

You can catch the exception, you just don’t.
The statement

  onOpenEvent.then((value) {
    completer.complete(HtmlWebSocketChannel(webSocket));
  });

listens on the onOpenEvent future, and creates a new Future as result.
When the onOpenEvent completes with the timeout exception, this then invocation gets the error, and since it doesn’t handle the exception, the future it returned also completes with the same exception.

Nobody ever touches that future since the statement above discards it, and therefore that future’s result is an unhandled error.

The reason your previous code worked was that await onOpenEvent would (re)throw that error and make it the result of the entire connect call.

The new code does not have a way for that error to reach the connect function call, the onOpenEvent.then(...) future might complete after connect has returned.

You can try adding an error handler to the then call:

  onOpenEvent.then((value) {
    completer.complete(HtmlWebSocketChannel(webSocket));
  }, onError: (error, StackTrace stackTrace) {
    if (!completer.isCompleted) completer.completeError(error, stackTrace);
  });
  webSocket.onError.first.then((err) {
    if (!completer.isCompleted) {    
      completer.completeError(WebSocketChannelException.from(err));
    }
  });

Answered By – lrn

Answer Checked By – Marie Seifert (FlutterFixes Admin)

Leave a Reply

Your email address will not be published. Required fields are marked *