1

I'm trying to set the onTimeout callback on a HttpClientRequest future's timeout method.

The example below compiles without error. However, when run it throws the following unhandled exception:

type '() => Future<HttpClientRequest>' is not a subtype of type '(() => FutureOr<_HttpClientRequest>)?' of 'onTimeout'.

I'm new to dart/flutter and am having trouble understanding why the callback method fails.

void main() async { requestify(); } Future<HttpClientRequest> requestify() async { var client = HttpClient(); var request = client.get('10.255.255.1', 80, ''); // request.timeout(const Duration(seconds: 1)); request.timeout(const Duration(seconds: 1), onTimeout: () { print('timed out'); return request; }); return request; } 
6
  • return request; inside the onTimeout function doesn't return the request to requestify function it returns it to the onTimeout function itself, but onTimeout expects a return type of FutureOr but the request is of type Future, just remove return request inside onTimeout function Commented Jun 10, 2022 at 15:07
  • If I remove return request; I get Error: A non-null value must be returned since the return type 'FutureOr<HttpClientRequest>' doesn't allow null. Commented Jun 10, 2022 at 15:16
  • you can return timeout message just like in the documentation: onTimeout: () => 'timeout', or you can remove the onTimeout completely since we care only about the duration. Commented Jun 10, 2022 at 15:20
  • If I use return 'my string'; I get Error: A value of type 'String' can't be returned from a function with return type 'FutureOr<HttpClientRequest>'. My understanding is that the documentation example returns 'timeout' because the example type is a Future<String>. Commented Jun 10, 2022 at 15:32
  • you're right sorry I didn't pay much attention, what about making the HttpClientRequest nullable meaning that if there is a timeout it will return null otherwise it returns a HttpClientRequest, and you can check if the response is not null Commented Jun 10, 2022 at 15:42

2 Answers 2

1

Let's look at the type error more closely:

type '() => Future<HttpClientRequest>' is not a subtype of type '(() => FutureOr<_HttpClientRequest>)?' of 'onTimeout'.

You supply a callback function that returns a Future<HttpClientRequest>.

What's expected is a function that returns a FutureOr<_HttpClientRequest>.

FutureOr<T> is a special union of types T and Future<T>, so a function that returns a Future<HttpCientRequest> is a subtype of (i.e., is compatible with) a function that returns a FutureOr<HttpClientRequest>. However, what's expected is a FutureOr<_HttpClientRequest>. _HttpClientRequest? Where did that come from, and why isn't that compatible?

Even without knowing the implementation details, we can presume that _HttpClientRequest is some subtype of HttpClientRequest. The error message implies that when we call HttpClient.get, the static (known at compile-time) type of the returned object is Future<HttpClientRequest>, but the actual runtime type is Future<_HttpClientRequest>. If type Derived is a subtype of Base, then Dart also treats GenericClass<Derived> as a subtype of GenericClass<Base>. Normally this is okay, and returning a narrower subtype of what a function is declared to return is safe.

The problem occurs when you then try to call .timeout on that returned Future<_HttpClientRequest>. Future<T>.timeout's callback must return a FutureOr<T>. However, you're calling .timeout on an object whose runtime type is Future<_HttpClientRequest>, so your callback must return an _HttpClientRequest too. Returning the base class type (HttpClientRequest) is not valid since you can't return a broader type where a narrower type is expected.

(See Dart gives Unhandled Exception: type is not a subtype of type of 'value' for another case where treating GenericClass<Derived> as a subtype of GenericClass<Base> can lead to surprising runtime errors.)

TL;DR

As far as the analyzer and compiler know, you're calling .timeout on a Future<HttpClientRequest> with a callback that returns an HttpClientRequest, so there's no compile-time error. However, at runtime, you're actually calling .timeout on a Future<_HttpClientRequest> with a callback that returns an HttpClientRequest, so you end up with a runtime error.

How can you fix this?

Some options:

  • Consider filing a Dart SDK issue about HttpClient.get returning an object whose runtime type is Future<_HttpClientRequest> instead of a Future<HttpClientRequest>.
  • Since _HttpClientRequest is a private type, you can't actually return an object of that type yourself. You can, however, use Future.value to construct a new Future<HttpClientRequest> out of the Future<_HttpClientRequest>:
     // `client.get` might return a `Future` that completes to a subtype of // `HttpClientRequest`. var request = Future<HttpClientRequest>.value(client.get('10.255.255.1', 80, '')); request.timeout(const Duration(seconds: 1), onTimeout: () { print('timed out'); return request; }); return request; } 
  • Avoid the weirdness with the callback type by not using Future.timeout with a callback and instead catching the resulting TimeoutException.

I'll also point out that Future.timeout returns a new Future, but you're returning the original Future, and the value returned by the timeout callback won't ever be used. I don't know if that's what you intend, although in this case it's probably fine if all you want to do is log that a request took too long.

Sign up to request clarification or add additional context in comments.

Comments

1
Future<HttpClientRequest?> requestify() async { try { var client = HttpClient(); var request = await client.get("10.255.255.1", 80, "") .timeout(const Duration(seconds: 1)); return request; } on TimeoutException catch(e) { return null; } } 

then in the main function

final response = await requestify(); if (response != null) { // HttpClientRequest } else { // timeout } 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.