Summary
Generated methods for @DioResponseType(ResponseType.stream) currently await the request future inside an async* method:
final _result = await _dio.fetch<ResponseBody>(_options);
final _value = _result.data!.stream;
yield* _value;
In cancellation scenarios, this can lead to post-cancel errors from the pending future surfacing as unhandled zone errors.
Why this is a problem
When the stream subscription is canceled while execution is suspended at await, a later error from that future may no longer be delivered through the stream listener lifecycle and can end up in the global zone.
In real apps, this often appears as noisy false-positive error reports in global collectors (e.g. Sentry/Firebase), even though stream consumer code is otherwise handling errors correctly.
Proposed change
Avoid await in this generated async* path and keep the request future in the stream pipeline:
final _result = _dio.fetch<ResponseBody>(_options);
final _value = _result.asStream().asyncExpand((response) => response.data!.stream);
yield* _value;
And for Stream<String>:
final _result = _dio.fetch<ResponseBody>(_options);
final _value = _result.asStream().asyncExpand(
(response) => response.data!.stream.map(utf8.decode),
);
yield* _value;
This preserves behavior but avoids the await/cancel race in the generator output.
Scope
- Affects generated code for
@DioResponseType(ResponseType.stream) (Stream<Uint8List> / Stream<String> paths).
- No API surface change.
- Intended as a minimal generator-only fix.
Additional context
I have a ready patch in my fork and can open a PR immediately if this direction is acceptable:
Summary
Generated methods for
@DioResponseType(ResponseType.stream)currently await the request future inside anasync*method:In cancellation scenarios, this can lead to post-cancel errors from the pending future surfacing as unhandled zone errors.
Why this is a problem
When the stream subscription is canceled while execution is suspended at
await, a later error from that future may no longer be delivered through the stream listener lifecycle and can end up in the global zone.In real apps, this often appears as noisy false-positive error reports in global collectors (e.g. Sentry/Firebase), even though stream consumer code is otherwise handling errors correctly.
Proposed change
Avoid
awaitin this generatedasync*path and keep the request future in the stream pipeline:And for
Stream<String>:This preserves behavior but avoids the await/cancel race in the generator output.
Scope
@DioResponseType(ResponseType.stream)(Stream<Uint8List>/Stream<String>paths).Additional context
I have a ready patch in my fork and can open a PR immediately if this direction is acceptable:
charlyChips:fix/stream-responsebody-cancellationtrevorwang/retrofit.dart:master