Flutter 中的异步事件处理之Stream处理、转换(六)

1,118 阅读15分钟

处理 Stream 的方法(返回结果)

Stream流的新建原理(_EmptyStream)

const factory Stream.empty() = _EmptyStream<T>;

工厂构造函数,用于创建一个空的广播流。这个流不包含任何数据,它的唯一作用是在被监听(subscribed)时发送一个 "done" 事件,表示流结束。

_EmptyStream

/// An empty broadcast stream, sending a done event as soon as possible.
class _EmptyStream<T> extends Stream<T> {
  const _EmptyStream();
  bool get isBroadcast => true;
  StreamSubscription<T> listen(void onData(T data)?,
      {Function? onError, void onDone()?, bool? cancelOnError}) {
    return new _DoneStreamSubscription<T>(onDone);
  }
}
  • StreamSubscription<T> listen(void onData(T data)?, {Function? onError, void onDone()?, bool? cancelOnError}):这是用于订阅(监听)流的方法。它接受一系列的回调函数,如 onData 用于处理流中的数据,onError 用于处理错误,onDone 用于在流完成时执行,cancelOnError 用于指定是否在发生错误时自动取消订阅。在这个 _EmptyStream 类中,listen 方法直接返回了一个 _DoneStreamSubscription<T> 对象,它会发送一个 "done" 事件,表示流已完成。

_EmptyStream 类,该类代表一个空的广播流。这个流没有实际的数据,只会立即发送一个 "done" 事件。当有监听者订阅这个流时,它会立即完成。这对于创建一个空的流并满足广播流的要求非常有用。

_DoneStreamSubscription

static const int _DONE_SENT = 1;
static const int _SCHEDULED = 2;
static const int _PAUSED = 4;

final Zone _zone;
int _state = 0;
void Function()? _onDone;

_DoneStreamSubscription(this._onDone) : _zone = Zone.current {
  _schedule();
}
  • static const int _DONE_SENT = 1;static const int _SCHEDULED = 2;static const int _PAUSED = 4;:这些是整数常量,表示 _DoneStreamSubscription 的内部状态。它们用于跟踪订阅的状态,如是否已发送 "done" 事件、是否已安排发送事件、是否已暂停。

  • final Zone _zone;:这是一个不可变的 _zone 属性,它存储了当前的 Dart 执行区域(Zone)。

  • int _state = 0;:这是一个整数属性 _state,用于跟踪订阅的状态。初始值为 0,表示订阅处于初始状态。

  • void Function()? _onDone;:这是一个可空的函数回调属性 _onDone,用于存储当 "done" 事件触发时要执行的函数。

  • _DoneStreamSubscription(this._onDone) : _zone = Zone.current:这是 _DoneStreamSubscription 类的构造函数。它接受一个 _onDone 回调函数作为参数,并将当前 Dart 执行区域(Zone)存储在 _zone 属性中。构造函数的主要目的是在创建订阅时立即安排发送 "done" 事件,以完成订阅。

    • _schedule() 方法用于安排发送 "done" 事件,即将 _state 属性的状态设置为 _SCHEDULED,并在当前 Zone 中执行 _sendDone 函数。

这个 _DoneStreamSubscription 类看起来是用于创建一个已完成的流订阅,当订阅被创建时,它会立即安排发送 "done" 事件,以通知订阅已完成。

bool get _isSent => (_state & _DONE_SENT) != 0;
bool get _isScheduled => (_state & _SCHEDULED) != 0;
bool get isPaused => _state >= _PAUSED;

它们用于检查 _DoneStreamSubscription 的内部状态:

  • bool get _isSent => (_state & _DONE_SENT) != 0;:这个属性用于检查 _state 是否包含 _DONE_SENT 标志位。如果 _state 中的 _DONE_SENT 位被设置,意味着 "done" 事件已经发送,此属性将返回 true,否则返回 false
  • bool get _isScheduled => (_state & _SCHEDULED) != 0;:这个属性用于检查 _state 是否包含 _SCHEDULED 标志位。如果 _state 中的 _SCHEDULED 位被设置,表示已经安排了发送 "done" 事件,此属性将返回 true,否则返回 false
  • bool get isPaused => _state >= _PAUSED;:这个属性用于检查 _state 是否大于或等于 _PAUSED。如果 _state 大于或等于 _PAUSED,表示订阅处于暂停状态,此属性将返回 true,否则返回 false

_schedule

void _schedule() {
  if (_isScheduled) return;
  _zone.scheduleMicrotask(_sendDone);
  _state |= _SCHEDULED;
}

该方法用于安排一个微任务 _sendDone 的执行,并更新了 _state 的状态以表示已经安排了任务。

具体来说:

  • _isScheduled 属性用于检查是否已经安排了任务。如果 _isScheduled 返回 true,则表示已经安排了任务,此时直接返回,不再重复安排任务。
  • _zone.scheduleMicrotask(_sendDone) 表示使用当前的 Dart Zone(通常是一个执行上下文,用于管理任务的执行)来安排一个微任务,该微任务执行 _sendDone 方法。微任务是一种异步任务,会在事件循环的下一个微任务队列中执行。
  • 最后,_state |= _SCHEDULED_SCHEDULED 标志位设置为 1,表示已经安排了任务。

通过这个 _schedule 方法,订阅在构造函数中创建时会被立即安排一个微任务来执行 _sendDone 方法,从而在订阅创建时发送 "done" 事件。这确保了 "done" 事件会尽快被发送,而不会等待下一个事件循环迭代。

onData、onError、onDone、pause、resume、cancel、asFuture

void onData(void handleData(T data)?) {}
void onError(Function? handleError) {}
void onDone(void handleDone()?) {
  _onDone = handleDone;
}

void pause([Future<void>? resumeSignal]) {
  _state += _PAUSED;
  if (resumeSignal != null) resumeSignal.whenComplete(resume);
}

void resume() {
  if (isPaused) {
    _state -= _PAUSED;
    if (!isPaused && !_isSent) {
      _schedule();
    }
  }
}

Future cancel() => Future._nullFuture;

Future<E> asFuture<E>([E? futureValue]) {
  E resultValue;
  if (futureValue == null) {
    if (!typeAcceptsNull<E>()) {
      throw ArgumentError.notNull("futureValue");
    }
    resultValue = futureValue as dynamic;
  } else {
    resultValue = futureValue;
  }
  _Future<E> result = new _Future<E>();
  _onDone = () {
    result._completeWithValue(resultValue);
  };
  return result;
}

StreamSubscription 接口的实现,它用于订阅一个流(Stream)。

  • onData: 这是一个回调函数,用于处理从流中接收到的数据。它接受一个参数 handleData,这个参数是一个函数,当数据到达时会被调用。但是在这个实现中,该方法没有实际的逻辑,因为它没有使用到 handleData 函数。
  • onError: 同样是一个回调函数,用于处理从流中接收到的错误。它接受一个参数 handleError,这个参数是一个函数,当出现错误时会被调用。但是在这个实现中,该方法也没有实际的逻辑,因为它没有使用到 handleError 函数。
  • onDone: 这是一个回调函数,用于处理流的结束事件("done" 事件)。它接受一个参数 handleDone,这个参数是一个函数,当流结束时会被调用。在这个实现中, _onDone 成员变量被赋值为 handleDone,表示当流结束时将调用这个函数。
  • pause: 用于暂停订阅。当调用 pause 方法时,_state 的状态会被更新,表示订阅已被暂停。如果提供了 resumeSignal 参数,那么在 resumeSignal 完成后,会自动调用 resume 方法来恢复订阅。
  • resume: 用于恢复订阅。当调用 resume 方法时,会检查当前订阅是否已经暂停(isPaused),如果是,则更新 _state 的状态来表示订阅已经恢复,并且如果没有在暂停状态且还没有发送 "done" 事件(_isSent 为假),则会调用 _schedule 方法来安排发送 "done" 事件。
  • cancel: 用于取消订阅。这个方法返回一个 Future,在这个实现中,它返回了一个完成状态为 nullFuture,表示取消操作已经完成。但是实际上,这个方法并没有执行取消订阅的操作,因为在这个具体的实现中,取消操作没有实际的逻辑。
  • asFuture: 用于将订阅转化为一个 Future。这个方法接受一个类型参数 E,以及一个可选的 futureValue 参数。它返回一个 Future,在 _onDone 回调触发时,会使用 resultValue 来完成这个 Future。如果 futureValuenull,则使用默认值 futureValue,否则使用传入的 futureValue。这个方法的目的是将订阅的结束事件映射为一个 Future,以便可以轻松地等待订阅的结束事件。

_sendDone

void _sendDone() {
  _state &= ~_SCHEDULED;
  if (isPaused) return;
  _state |= _DONE_SENT;
  var doneHandler = _onDone;
  if (doneHandler != null) _zone.runGuarded(doneHandler);
}

StreamSubscription 接口中的 _sendDone 方法的实现。它的作用是发送 "done" 事件给订阅者。

让我来逐步解释这个方法的各个部分:

  1. _state &= ~_SCHEDULED;: 这一行代码用于清除 _state 中与 _SCHEDULED 标志位相关的状态,表示不再安排 "done" 事件发送。
  2. if (isPaused) return;: 这一行代码检查订阅是否处于暂停状态,如果是暂停状态,就不会发送 "done" 事件。这是因为当订阅处于暂停状态时,它不应该接收到任何事件。
  3. _state |= _DONE_SENT;: 这一行代码设置 _state_DONE_SENT 标志位,表示 "done" 事件已经发送。
  4. var doneHandler = _onDone;: 这一行代码将 _onDone 方法存储在 doneHandler 变量中,以便稍后调用。
  5. if (doneHandler != null) _zone.runGuarded(doneHandler);: 最后,如果 doneHandler 不为空(即存在 "done" 事件的处理程序),则使用 _zone.runGuarded 方法来安全地运行 doneHandler。这确保了即使 "done" 事件处理程序引发异常,也不会导致整个程序崩溃。

这个方法的主要作用是在合适的时机发送 "done" 事件给订阅者,并在发送之前进行一些状态的管理和检查,以确保事件被正确处理。

新建一个空的流,程序运行后, 返回Done

const stream = Stream.empty();
 stream.listen(
   (value) {
     throw "Unreachable";
   },
   onDone: () {
     print('Done');
   },
 );

any

Future<bool> any(bool test(T element)) {}

final result = await Stream.periodic(const Duration(seconds: 1), (count) => count).take(15).any((element) => element >= 5);
print(result); // true

判断流中是否有任何元素满足给定的条件 test,如果有,则返回 true,否则返回 false

firstWhere

Future<T> firstWhere(bool test(T element), {T orElse()?}) {}

var result = await Stream.fromIterable([1, 3, 4, 9, 12]).firstWhere((element) => element % 6 == 0, orElse: () => -1);
print(result); // 12

result = await Stream.fromIterable([1, 2, 3, 4, 5]).firstWhere((element) => element % 6 == 0, orElse: () => -1);
print(result); // -1

在流中查找第一个满足给定条件 test 的元素,如果找到则返回该元素,否则返回一个默认值(通过 orElse 参数指定),如果没有指定 orElse 参数,且没有找到满足条件的元素,则会抛出 IterableElementError.noElement() 错误。

lastWhere

Future<T> lastWhere(bool test(T element), {T orElse()?}) {}

var result = await Stream.fromIterable([1, 3, 4, 7, 12, 24, 32]).lastWhere((element) => element % 6 == 0, orElse: () => -1);
print(result); // 24
result = await Stream.fromIterable([1, 3, 4, 7, 12, 24, 32]).lastWhere((element) => element % 10 == 0, orElse: () => -1);
print(result); // -1

在流中查找最后一个满足给定条件 test 的元素,如果找到则返回该元素,否则返回一个默认值(通过 orElse 参数指定),如果没有指定 orElse 参数,且没有找到满足条件的元素,则会抛出 IterableElementError.noElement() 错误。

reduce

Future<T> reduce(T combine(T previous, T element)) {}

final result = await Stream.fromIterable([2, 6, 10, 8, 2]).reduce((previous, element) => previous + element);
print(result); // 28

在流中对元素进行累积操作,使用指定的 combine 函数来计算最终的结果。这个函数将按顺序处理流中的元素,将上一个计算结果和当前元素传递给 combine 函数,并将计算结果作为下一次计算的输入,最终返回一个 Future<T>,该 Future 在流结束后完成,包含累积的结果。

every

Future<bool> every(bool test(T element)) {}

final result =await Stream.periodic(const Duration(seconds: 1), (count) => count)
         .take(15).every((x) => x <= 5);
print(result); // false

every 方法用于检查流中的所有元素是否都满足指定的测试条件,即测试函数 test。如果所有元素都满足条件,则返回的 Future 完成为 true,否则完成为 false

singleWhere

Future<T> singleWhere(bool test(T element), {T orElse()?}) {}

var result = await Stream.fromIterable([1, 2, 3, 6, 9, 12])
    .singleWhere((element) => element % 4 == 0, orElse: () => -1);
print(result); // 12

result = await Stream.fromIterable([2, 6, 8, 12, 24, 32])
    .singleWhere((element) => element % 9 == 0, orElse: () => -1);
print(result); // -1

result = await Stream.fromIterable([2, 6, 8, 12, 24, 32])
  .singleWhere((element) => element % 6 == 0, orElse: () => -1);
 Throws.

singleWhere 方法用于查找流中满足指定测试条件的唯一元素。如果找到符合条件的元素并且只有一个满足条件的元素,它将返回该元素。如果没有或有多个满足条件的元素,将根据提供的 orElse 参数或抛出异常来处理。

drain

Future<E> drain<E>([E? futureValue]) {}

final result = await Stream.fromIterable([1, 2, 3]).drain(100);
print(result); // Outputs: 100.

drain 方法用于监听流的事件,但不对数据事件执行任何处理,直到流结束。一旦流结束,它将返回一个 Future,用于表示处理结束后的状态。

处理 Stream 的方法(返回新Stream)

map

final stream = Stream<int>.periodic(const Duration(seconds: 1), (count) => count)
.take(5);

final calculationStream = stream.map<String>((event) => 'Square: ${event * event}');
calculationStream.forEach(print);
Square: 0
Square: 1
Square: 4
Square: 9
Square: 16

Stream<S> map<S>(S convert(T event)) {
  return new _MapStream<T, S>(this, convert);
}

map 方法用于创建一个新的流,该流的事件是原始流事件经过某个转换函数转换后的结果。

_MapStream

typedef T _Transformation<S, T>(S value);

/// A stream pipe that converts data events before passing them on.
class _MapStream<S, T> extends _ForwardingStream<S, T> {
  final _Transformation<S, T> _transform;

  _MapStream(Stream<S> source, T transform(S event))
      : this._transform = transform,
        super(source);

  void _handleData(S inputEvent, _EventSink<T> sink) {
    T outputEvent;
    try {
      outputEvent = _transform(inputEvent);
    } catch (e, s) {
      _addErrorWithReplacement(sink, e, s);
      return;
    }
    sink._add(outputEvent);
  }
}

它是一个流管道,用于在将数据事件传递之前对其进行转换。

  1. typedef T _Transformation<S, T>(S value);:这是一个泛型函数类型别名,用于表示将类型 S 的值转换为类型 T 的转换函数。
  2. _MapStream<S, T> extends _ForwardingStream<S, T>_MapStream 类继承自 _ForwardingStream,这意味着它是一个流的装饰器,可以在原始流上添加功能。
  3. final _Transformation<S, T> _transform;:这是一个私有字段,用于存储将应用于数据事件的转换函数。
  4. _MapStream(Stream<S> source, T transform(S event)):这是 _MapStream 的构造函数,它接受两个参数。source 是原始流,transform 是转换函数。构造函数将转换函数 _transform 初始化为传递的 transform 函数,并调用了父类 _ForwardingStream 的构造函数,将原始流传递给它。
  5. _handleData(S inputEvent, _EventSink<T> sink):这是一个私有方法,用于处理数据事件。它接受两个参数:inputEvent 是原始流中的数据事件,sink 是新流的事件接收器。在此方法中,它将输入事件 inputEvent 传递给 _transform 函数,将其转换为类型 T 的事件。如果转换失败(例如,抛出异常),则会通过 _addErrorWithReplacement 方法通知事件接收器,传递异常信息。

_MapStream 类的主要目的是在原始流事件传递到新流之前,通过应用 _transform 函数来对事件进行转换。

_ForwardingStream

/// A [Stream] that forwards subscriptions to another stream.
///
/// This stream implements [Stream], but forwards all subscriptions
/// to an underlying stream, and wraps the returned subscription to
/// modify the events on the way.
///
/// This class is intended for internal use only.
abstract class _ForwardingStream<S, T> extends Stream<T> {
  final Stream<S> _source;

  _ForwardingStream(this._source);

  bool get isBroadcast => _source.isBroadcast;

  StreamSubscription<T> listen(void onData(T value)?,
      {Function? onError, void onDone()?, bool? cancelOnError}) {
    return _createSubscription(onData, onError, onDone, cancelOnError ?? false);
  }

  StreamSubscription<T> _createSubscription(void onData(T data)?,
      Function? onError, void onDone()?, bool cancelOnError) {
    return new _ForwardingStreamSubscription<S, T>(
        this, onData, onError, onDone, cancelOnError);
  }

  // Override the following methods in subclasses to change the behavior.

  void _handleData(S data, _EventSink<T> sink);

  void _handleError(Object error, StackTrace stackTrace, _EventSink<T> sink) {
    sink._addError(error, stackTrace);
  }

  void _handleDone(_EventSink<T> sink) {
    sink._close();
  }
}

一个抽象类 _ForwardingStream<S, T>,它代表一个将订阅传递给另一个流的流。

  1. abstract class _ForwardingStream<S, T> extends Stream<T>:这是一个抽象类,代表一个流的装饰器。它有两个泛型参数 ST,分别表示原始流的事件类型和新流的事件类型。
  2. final Stream<S> _source:这是一个私有字段,表示原始流,即将接收所有订阅的流。
  3. _ForwardingStream(this._source):这是 _ForwardingStream 的构造函数,它接受一个 Stream<S> 类型的参数 _source,并将其存储在私有字段 _source 中。
  4. bool get isBroadcast => _source.isBroadcast:这是一个 getter 方法,用于获取新流是否是广播流。它通过查询原始流 _sourceisBroadcast 属性来确定。
  5. StreamSubscription<T> listen(void onData(T value)?, {Function? onError, void onDone()?, bool? cancelOnError}):这是 Stream 类的标准方法,用于订阅流的事件。在 _ForwardingStream 中,它会调用 _createSubscription 方法创建一个新的事件订阅,并将订阅的回调函数和参数传递给 _createSubscription 方法。
  6. StreamSubscription<T> _createSubscription(void onData(T data)?, Function? onError, void onDone()?, bool cancelOnError):这是一个抽象方法,需要在子类中实现。它用于创建新的事件订阅。在 _ForwardingStream 中,它创建了一个 _ForwardingStreamSubscription,该订阅将事件处理委托给 _handleData_handleError_handleDone 方法。
  7. void _handleData(S data, _EventSink<T> sink):这是一个抽象方法,需要在子类中实现。它用于处理原始流传递的数据事件,并将其转发给新流的事件接收器。子类需要实现此方法以定义如何处理数据事件。
  8. void _handleError(Object error, StackTrace stackTrace, _EventSink<T> sink):这是一个抽象方法,需要在子类中实现。它用于处理原始流传递的错误事件,并将其转发给新流的事件接收器。子类需要实现此方法以定义如何处理错误事件。
  9. void _handleDone(_EventSink<T> sink):这是一个抽象方法,需要在子类中实现。它用于处理原始流传递的完成事件,并将其转发给新流的事件接收器。子类需要实现此方法以定义如何处理完成事件。

_ForwardingStream 类是一个用于创建流装饰器的抽象类,它可以用于修改原始流的事件处理方式,以创建新的流。子类需要实现 _handleData_handleError_handleDone 方法,以定义新流的行为。

_source是原输入流。

流的转换, 原理上是新建了一个流。

skip

final stream = Stream<int>.periodic(const Duration(seconds: 1), (i) => i).skip(7);
stream.forEach(print); // Skips events 0, ..., 6. Outputs events: 7, ...

Stream<T> skip(int count) {
  return new _SkipStream<T>(this, count);
}

创建一个新的流 _SkipStream<T>,该流会跳过原始流中的前 count 个事件。以下是代码的逐行解释:

  1. Stream<T> skip(int count):这是一个方法签名,表示创建一个新的流,该流会跳过原始流中的前 count 个事件,并返回新的流。
  2. return new _SkipStream<T>(this, count);:在 skip 方法中,它创建了一个 _SkipStream 的实例,并传递两个参数:thiscountthis 表示调用 skip 方法的原始流,而 count 表示要跳过的事件数量。

这个 _SkipStream 类的目的是创建一个新的流,该流会忽略原始流中的前 count 个事件。它通过在订阅时跳过这些事件来实现这一行为。这样,订阅新的 _SkipStream 后,最初的 count 个事件将不会传递给订阅者。

where

final stream = Stream<int>.periodic(const Duration(seconds: 1), (count) => count)
.take(10);

final customStream = stream.where((event) => event > 3 && event <= 6);
customStream.listen(print); // Outputs event values: 4,5,6.

Stream<T> where(bool test(T event)) {
  return new _WhereStream<T>(this, test);
}

一个基于时间间隔的周期性流 stream,该流每隔1秒生成一个事件,事件值为生成事件的次数,然后限制了生成的事件数量为10个。

接着,它创建了一个名为 customStream 的新流,这个流通过在原始 stream 上应用 where 操作,筛选出事件值大于3且小于等于6的事件。

最后,它调用 customStream.listen(print),订阅了 customStream,并将事件值打印到控制台。因为 customStream 筛选出的事件值只包括4、5和6,所以在 listen 中打印的事件值将会是 4、5 和 6。

where 方法,它是用来创建一个新的流,该流会在原始流的事件上应用给定的测试函数 test。只有测试函数返回 true 的事件才会传递给新的流,其他事件都会被过滤掉。在这里,test 函数检查事件是否大于3且小于等于6,因此 customStream 中只包含满足这个条件的事件。

参考资料

异步编程:使用 stream

【Flutter 异步编程 - 拾】 | 探索 Stream 的转换原理与拓展