虽然经常会问这类问题,但我认为我有一个更具体的约束条件,使问题更有趣。我正在使用MVC模式在Dart中编写客户端应用程序。我的目标很简单:听取按钮的点击,触发对后端API的异步请求,并将该数据呈现给用户。
最少,我有一个模型,视图和控制器类。模型类实现了发出请求并捆绑其接收的数据的方法。视图类将感兴趣的DOM子树作为字段,并实现操作其中元素的方法。控制器具有单个实例,每个模型和视图类作为其字段,并在视图元素上注册事件处理程序。控制器的事件处理程序触发对模型的调用以发出请求并返回数据,然后将数据传递给视图进行渲染。
当我尝试将来自异步请求的传入数据捕获到模型的实例变量中时,会出现问题。我想保留所有封装的内容(这就是我首先使用Dart的原因),并且我想避免使用全局变量来保存来自异步请求的数据。我当前布局的最小示例如下所示。为了清楚起见,我已经将所有的领域和方法公之于众。
// view.dart
class FooView {
// The root element of the view with which we're concerned.
static final Element root = querySelector('#thisView');
FooView() { init(); }
void init() { root.hidden = false; }
// Appends the new data into an unordered list.
void update(List<Map<String,String>> list) {
UListElement container = root.querySelector('ul#dataContainer');
container
..hidden = true
..children.clear();
for ( Map<String,String> item in list ) {
container.append(new LIElement()
..id = item['id']
..text = item['text']
);
container.hidden = false;
}
// model.dart
class FooModel {
// Instance variable to hold processed data from the async request.
List<Map<String,String>> dataList;
// Makes async request, returning data to caller.
List<Map<String,String>> getData() {
HttpRequest
.getString('example.com/api/endpoint')
.then( (String data) {
dataList = JSON.decode(data);
});
return dataList;
}
}
// controller.dart
class FooController {
FooModel model;
FooView view;
FooController() {
model = new FooModel;
view = new FooView;
}
void registerHandlers() {
// When this button is clicked, the view is updated with data from the model.
ButtonElement myButton = view.root.querySelector('#myButton');
myButton.onClick.listen( (Event e) {
view.update(model.getData());
});
}
}
我看到的错误涉及model.dataList
字段在所有这些结束时出现null
。我的第一个脸红是我不理解回调函数的范围。我第一次理解它的方式,回调将在它到达时处理请求的数据,并在准备好时设置实例变量。也许实例变量在回调范围内是别名和修改的,但是我想要返回的变量永远不会被触及。
我考虑过将Future
对象传递给视图的方法,然后只进行处理,并将元素作为副作用添加到DOM中。这种技术会破坏我的MVC设计(甚至超过现在这个最小的工作示例中的破坏)。
我也很可能完全错误地使用异步编程。考虑到这一点,我的异步调用是没用的,因为当事件触发时,我基本上会在控制器中对view.update()
进行阻塞调用。也许我应该将一个请求Future
传递给控制器,并在触发事件处理程序时从那里激活请求的then()
方法。
在Dart中,回调函数驻留在什么范围内,如何从中获取数据,副作用最小,封装最大化?
N.B。我讨厌讨论这个经常讨论的问题,但我已经阅读了类似问题的先前答案,但无济于事。
答案 0 :(得分:2)
getData
方法启动异步HTTP请求,然后在收到/解析响应之前立即返回。这就是为什么model.datalist
是null
。
要以最小的努力完成这项工作,您可以使getData
同步:
(注意:我更改了dataList
类型,只是为了使其适用于示例JSON服务http://ip.jsontest.com/)
// model.dart
class FooModel {
// Instance variable to hold processed data from the async request.
Map<String, String> dataList;
// Makes async request, returning data to caller.
Map<String, String> getData() {
var request = new HttpRequest()
..open('GET', 'http://ip.jsontest.com/', async: false)
..send();
dataList = JSON.decode(request.responseText);
return dataList;
}
}
虽然这可能违反了您的目标,但我同意您的疑虑:阻止呼叫,并会亲自考虑保持HTTP请求异步并使getData
返回引用您的模型类或已解析数据的新未来。就像是:
// model.dart
class FooModel {
// Instance variable to hold processed data from the async request.
Map<String,String> dataList;
// Makes async request, returning data to caller.
Future<Map<String, String>> getData() {
return HttpRequest
.getString('http://ip.jsontest.com/')
.then( (String data) {
dataList = JSON.decode(data);
return dataList;
});
}
}
并在控制器中:
void registerHandlers() {
// When this button is clicked, the view is updated with data from the model.
ButtonElement myButton = FooView.root.querySelector('#myButton');
myButton.onClick.listen( (Event e) {
model.getData().then((Map<String, String> dataList) {
view.update(dataList);
});
});
}
答案 1 :(得分:1)
您可以使用Stream
使您的设计松散耦合和异步:
class ModelChange {...}
class ViewChange {...}
abstract class Bindable<EventType> {
Stream<EventType> get updateNotification;
Stream<EventType> controllerEvents;
}
class Model implements Bindable<ModelChange> {
Stream<ModelChange> controllerEvents;
Stream<ModelChange> get updateNotification => ...
}
class View implements Bindable<ViewChange> {
Stream<ViewChange> controllerEvents;
Stream<ViewChange> get updateNotification => ...
}
class Controller {
final StreamController<ViewChange> viewChange = new StreamController();
final StreamController<ModelChange> modelChange = new StreamController();
Controller.bind(Bindable model, Bindable view) {
view.controllerEvents = viewChange.stream;
model.controllerEvents = modelChange.stream;
view.updateNotification.forEach((ViewChange vs) {
modelChange.add(onViewChange(vs));
});
model.updateNotification.forEach((ModelChange mc) {
viewChange.add(onModelChange(mc));
});
}
ModelChange onViewChange(ViewChange vc) => ...
ViewChange onModelChange(ModelChange mc) => ...
}
答案 2 :(得分:1)
在HttpRequest返回之前,您在datalist
返回getData
。
// Makes async request, returning data to caller.
List<Map<String,String>> getData() {
return HttpRequest // <== modified
.getString('example.com/api/endpoint')
.then( (String data) {
return JSON.decode(data); // <== modified
});
// return dataList; // <== modified
void registerHandlers() {
// When this button is clicked, the view is updated with data from the model.
ButtonElement myButton = view.root.querySelector('#myButton');
myButton.onClick.listen( (Event e) {
model.getData().then((data) => view.update(data)); // <== modified
});
}