取消流onData

时间:2017-06-09 06:28:41

标签: dart

我有一个事件总线处理我的应用程序的所有中央事件。我有一个特殊情况,我有一系列异步操作,我只想执行一次(当一个特殊事件发生时),所以我必须启动异步函数一,失去控制其他函数将触发一个事件我的第二个动作,等等。

所以我需要启动一个动作,然后听取等待动作一的事件总线触发(直接)一个将启动动作二等的事件......

当然,一旦序列的每个元素被执行,我想停止听取触发它的事件。

我为此设想了一个consumeOnce(事件,动作)函数,它将订阅总线,等待预期的事件,在接收事件时执行动作,并在动作启动后立即取消订阅(异步)

  final StreamController<Map<PlaceParam, dynamic>> _controller =
  new StreamController<Map<PlaceParam, dynamic>>.broadcast();

  void consumeOnce(PlaceParam param, Function executeOnce) {
    StreamSubscription subscription = _controller.stream.listen((Map<PlaceParam, dynamic> params) {
      if(params.containsKey(param)) {
        executeOnce();
        subscription.cancel(); //can't access, too early: not created yet
      }
    });
  }

问题是我无法访问回调正文中的变量订阅,因为它当时仍未创建

由于听众没有在他们的订阅顺序中执行任何保证,我无法注册将删除我的订阅的另一个订阅者(即使执行顺序得到保证,我仍然会找到我自己的订阅,我无法删除:负责删除原始订阅的人... ...

有什么想法吗?

这种模式可以解决我的问题,但我发现它并不优雅:

@Injectable()
class EventBus<K, V> {
  final StreamController<Map<PlaceParam, dynamic>> _controller =
  new StreamController<Map<PlaceParam, dynamic>>.broadcast();

  Future<Null> fire(Map<PlaceParam, dynamic> params) async {
    await _controller.add(params);
  }

  Stream<Map<PlaceParam, dynamic>> getBus() {
    return _controller.stream;
  }


  void consumeOnce(PlaceParam param, Function executeOnce) {
    SubscriptionRemover remover = new SubscriptionRemover(param, executeOnce);
    StreamSubscription subscription = _controller.stream.listen(remover.executeOnce);
    remover.subscription = subscription;
  }
}

class SubscriptionRemover {
  PlaceParam param;
  Function executeOnce;
  StreamSubscription subscription;

  SubscriptionRemover(this.param, this.executeOnce);

  void execute(Map<PlaceParam, dynamic> params) {
    if (params.containsKey(param)) {
      executeOnce();
      subscription.cancel();
    }
  }
}

但我不喜欢它,因为从理论上讲,事件可能发生在两个电话之间:

    StreamSubscription subscription = _controller.stream.listen(remover.executeOnce); //event may occur now!!!
    remover.subscription = subscription;

我认为方法的存在:_controller.stream.remove(Function fn)会更加直接和清晰。

我是对的吗?还是有一种我没想到的方式?

3 个答案:

答案 0 :(得分:6)

  

问题是我无法在回调体中访问变量订阅,因为它当时仍未创建

这不完全正确 - 您无法访问的是subscription 变量。 Dart不允许变量声明引用自己。当变量只在一个尚未执行的闭包中被引用时,这偶尔会令人讨厌。

解决方案是预先声明变量或在听完后更新onData - 监听器:

var subscription;  // Pre-declare variable (can't be final, though)
subscription = stream.listen((event) {
  .... subscription.cancel();
});

final subscription = stream.listen(null);
subscription.onData((event) {  // Update onData after listening.
  .... subscription.cancel(); ....
});

在某些情况下,您仍然无法访问订阅对象,但只有当流中断Stream合同并且在收听时立即开始发送事件时,才有可能。 Streams不能这样做,他们必须等到稍后的微任务才能发送第一个事件,这样调用listen的代码就有时间接收订阅并将其分配给一个变量。使用同步流控制器违反合同是可能的(这是明智地使用同步流控制器的一个原因)。

答案 1 :(得分:5)

只需预先声明变量,然后就可以在回调中访问它:

StreamSubscription subscription;
subscription = _controller.stream.listen((Map<PlaceParam, dynamic> params) {
  if(params.containsKey(param)) {
    executeOnce();
    subscription.cancel(); 
  }
});

答案 2 :(得分:2)

只调用一次的回调是粗略的。我认为返回Future会更多Dart-y:

Future<Null> consumeOnce(PlaceParam param) async {
  await _controller.stream.singleWhere((params) => params.containsKey(param));
}

鉴于这是一个单行程,我不确定它是否真的有必要,除非它发生了很多。 EventBus的客户可以轻松调用getBus().singleWhere

如果你真的喜欢使用回调的想法,你可以保留executeOnce参数并在等待singleWhere的调用后调用它,但我真的不能想到这样的情况这会更好。