2

この種の質問はよく聞かれますが、問題をもう少し興味深いものにする、より具体的な制約があると思います。MVC パターンを使用して Dart でクライアント側アプリケーションを作成しています。私の目標は単純です。ボタンのクリックをリッスンし、バックエンド API への非同期要求をトリガーし、そのデータをユーザーに提示します。

少なくとも、モデル、ビュー、およびコントローラー クラスをそれぞれ 1 つずつ持っています。モデル クラスは、リクエストを作成し、受信したデータをまとめるメソッドを実装します。ビュー クラスは、関心のある DOM サブツリーをフィールドとして持ち、その中の要素を操作するメソッドを実装します。コントローラーは、モデル クラスとビュー クラスのそれぞれのインスタンスを 1 つフィールドとして持ち、ビューの要素にイベント ハンドラーを登録します。コントローラーのイベント ハンドラーは、モデルへの呼び出しを開始して要求を行い、データを返します。データはレンダリングのためにビューに渡されます。

非同期リクエストからモデルのインスタンス変数に受信データをキャプチャしようとすると、問題が発生します。すべてを適切にカプセル化しておきたい (そのため、最初に 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 では、コールバック関数はどのスコープに存在し、最小限の副作用と最大限のカプセル化でそれらからデータを取得するにはどうすればよいですか?

注意: このよく議論される質問について詳しく説明するのは嫌いですが、同様の質問に対する以前の回答を読みましたが、役に立ちませんでした。

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

HttpRequest が戻る前に戻りますdatalistgetData

  // 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 に答える