2

虽然这类问题经常被问到,但我认为我有一个更具体的限制,使问题变得更有趣。我正在使用 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 中,回调函数驻留在什么范围内,如何以最小的副作用和最大的封装从它们中获取数据?

NB 我讨厌详细讨论这个经常讨论的问题,但我已经阅读了以前对类似问题的回答,但无济于事。

4

3 回答 3

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);
    });
  });
}
于 2014-07-22T00:07:05.770 回答
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) => ...
}
于 2014-07-22T01:51:25.357 回答
1

datalistgetDataHttpRequest 返回之前返回。

  // 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
    });
  }
于 2014-07-22T04:19:51.800 回答