等待我的班级初始化(或如何等待 Future 完成)?

Posted

技术标签:

【中文标题】等待我的班级初始化(或如何等待 Future 完成)?【英文标题】:Waiting for my class to initialize (or how to wait for a Future to complete)? 【发布时间】:2014-07-06 01:59:51 【问题描述】:

Dart 中的期货是我存在的祸根。

我有一个类,它调用异步(Future)函数来启动数据库实例,如下所示:

class DataManager 
  bool DbIsReady = false;
  Db _db;

  DataManager() 
    init_mongo_db();
  

  void init_mongo_db() 
    print("Initializing MongoDB");
    _db = new Db("mongodb://127.0.0.1/test");
    _db.open().then((_) 
      DbIsReady = true;
    );
    

  Future<List> attemptLogin(String username, String password) 
    users = _db.collection("users");

    return // ... rest of code cut out for clarity
  

这在服务器端可以正常工作,因为当服务器第一次启动时,数据库会被初始化。当用户实际尝试登录时,数据库已经初始化并且一切正常。但是,当我尝试为此创建集成测试时,这会惨遭失败。当我创建类时,数据库尚未初始化,因此运行 AttemptLogin 例程失败。

DataManager db = new DataManager();
// This fails miserably, because db hasn't initialized yet.
db.AttemptLogin("test", "test"); 

更糟糕的是,代码使用了 Dart DI 框架,因此我实际上无法直接控制 DataManager 类的初始化。这是该类的实际设置:

setUp(() 
  app.addModule(new Module()
            ..bind(DataManager)
  app.setUp();
);

然后这是测试登录功能的调用,最终调用失败的尝试登录函数:

var req = new MockRequest("/user/login", contentType: 'JSON', method:'POST',
    body:JSON.encode(
      "username" : 'test',
       "password" : 'test' ));

如何处理数据库初始化的异步特性并仍然进行模拟测试?具体来说,有没有办法强制尝试登录()未来以某种方式等待 DataManager 类初始化的完成?

感谢您的帮助, 格雷格

【问题讨论】:

【参考方案1】:

如果你的类有异步设置,你就不能使用普通的构造函数。 在这种情况下,我只使用返回未来的工厂方法:

class DataManager 
  final Db _db;
  DataManager._(this._db);
  static Future<DataManager> createNew() 
    var db = new Db("mongodb://127.0.0.1/test");
    return db.open().then((_) => new DataManager._(db));
  
  ...
;

我不知道你是如何将它连接到你的依赖注入框架中的,但我想它可以用静态方法做一些事情。

【讨论】:

您可以使用将来的等待来使异步同步。但是,从长远来看,最好只返回一个未来。 Future.wait 仍然返回一个Future,它可以一次等待多个Future,并返回一个带有结果列表的Future。没有办法将异步结果变成同步结果,因为同步结果需要在函数返回时可用,而异步结果还没有。 我同意 lrn 对此的理解——future.wait 不会暂停程序执行。 @lrn:我在我的程序中尝试了你的答案,但没有成功。 DI 框架(原来是标准的 dart DI 框架:pub.dartlang.org/packages/di)尝试注入 _Future 对象并失败。错误消息是“无法执行 server_lib.IsUserLoggedIn - 类型 '_Future' 不是 'db' 的类型 'DataManager' 的子类型。” 看起来di 框架不处理异步创建的值。也许向作者提出要求。它显然还只是一个原型(根据github.com/angular/di.dart)。【参考方案2】:

使用@lrn 的解决方案怎么样

setUp(() 
  return DataManager.createNew().then((dm) 
    app.addModule(new Module()
            ..bind(DataManager, toValue: dm);
    app.setUp();
  
);

这样你已经将一个初始化的DataManager 实例传递给了DI。如果您稍后请求它,您始终可以确定它已经初始化。

【讨论】:

这看起来很合理——我今晚晚些时候要试试这个。不是 100% 确定 setUp() 函数也允许 Futures,但值得一试。 unittest 方法对期货有特定的支持。如果您返回未来,则仅在未来返回时执行下一步。在这种情况下,在createNew.then(()...) returns 之前不会启动任何测试这对于test('xxx', () return someAsync(); 也是如此,其中测试运行程序在评估测试结果之前等待未来返回。提示:很容易忘记,如果你有嵌套的异步方法,每个方法都应该在调用前有一个 return 以拥有一个完全连接的链。 代码完美运行!每天学些新东西。 :) 谢谢,君特!这似乎是解决这个问题的最明智的方法,所以我将朝着这个方向前进。

以上是关于等待我的班级初始化(或如何等待 Future 完成)?的主要内容,如果未能解决你的问题,请参考以下文章

等待一个函数在 ListView 中完成

如何让一个方法在继续执行之前等待另一个(异步)方法完成?

颤振:如何使用“等待”等待其他 BLoC 事件完成

利用多线程实现Future模式

如何在我的代码中等待来自其他地方的异步链的最终结果?

颤振等待未来更长的时间?