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;
}
CodePudding user response:
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 isFuture<_HttpClientRequest>
instead of aFuture<HttpClientRequest>
. - Since
_HttpClientRequest
is a private type, you can't actually return an object of that type yourself. You can, however, useFuture.value
to construct a newFuture<HttpClientRequest>
out of theFuture<_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 resultingTimeoutException
.
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.
CodePudding user response:
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
}