我有一个事件总线处理我的应用程序的所有中央事件。我有一个特殊情况,我有一系列异步操作,我只想执行一次(当一个特殊事件发生时),所以我必须启动异步函数一,失去控制其他函数将触发一个事件我的第二个动作,等等。
所以我需要启动一个动作,然后听取等待动作一的事件总线触发(直接)一个将启动动作二等的事件......
当然,一旦序列的每个元素被执行,我想停止听取触发它的事件。
我为此设想了一个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)会更加直接和清晰。
我是对的吗?还是有一种我没想到的方式?
答案 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
的调用后调用它,但我真的不能想到这样的情况这会更好。