Ожидание инициализации моего класса (или как дождаться завершения Future)?

Будущее в Dart - проклятие моего существования.

У меня есть класс, который вызывает функцию async (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();
});

И затем это вызов для проверки функциональности входа в систему, который в конечном итоге вызывает функцию попыткиLogin, которая терпит неудачу:

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

Как справиться с асинхронным характером инициализации базы данных и по-прежнему проводить имитационное тестирование? В частности, есть ли способ заставить попыткуLogin () Future как-то дождаться завершения инициализации класса DataManager?

Спасибо за помощь, Грег


person Greg Sherman    schedule 06.07.2014    source источник


Ответы (2)


как насчет использования решения @lrn s, например

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

Таким образом, вы уже передаете инициализированный экземпляр DataManager в DI. Если вы запросите его позже, вы всегда можете быть уверены, что он уже инициализирован.

person Günter Zöchbauer    schedule 10.07.2014
comment
Это выглядит разумным - я собираюсь попробовать это позже сегодня вечером. Не уверен на 100%, что функция setUp () также позволяет использовать Futures, но попробовать стоит. - person Greg Sherman; 10.07.2014
comment
Методы unittest имеют специальную поддержку для фьючерсов. Если вы возвращаете будущее, следующий шаг выполняется только тогда, когда возвращается будущее. В этом случае тесты не запускаются до createNew.then(()...) returns. Это также верно для test('xxx', () { return someAsync(); }, где средство тестирования ожидает возврата в будущем, прежде чем будет оценен результат теста. Подсказка: легко забыть, если у вас есть вложенные асинхронные методы, каждый из которых должен иметь возврат перед вызовом, чтобы получить полностью связанную цепочку. - person Günter Zöchbauer; 10.07.2014
comment
Код работал отлично! Узнавайте что-то новое каждый день. :) Спасибо, Гюнтер! Это кажется наиболее разумным подходом к проблеме, поэтому я продолжу в этом направлении. - person Greg Sherman; 11.07.2014

Если ваш класс имеет асинхронную настройку, вы не можете использовать обычный конструктор. В этом случае я бы просто использовал фабричный метод, возвращающий будущее:

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));
  }
  ...
};

Я не знаю, как вы подключаете это к своей структуре внедрения зависимостей, но я думаю, что он может что-то делать с помощью статических методов.

person lrn    schedule 06.07.2014
comment
Вы можете использовать future wait, чтобы синхронизировать асинхронность. Однако в долгосрочной перспективе лучше просто вернуть будущее. - person ptDave; 06.07.2014
comment
Future.wait по-прежнему возвращает Future, он может просто ждать сразу нескольких фьючерсов и возвращать одно будущее со списком результатов. Невозможно превратить асинхронный результат в синхронный результат, потому что синхронный результат должен быть доступен при возврате функции, а асинхронного результата еще нет. - person lrn; 07.07.2014
comment
Я согласен с пониманием этого lrn - future.wait не приостанавливает выполнение программы. - person Greg Sherman; 07.07.2014
comment
@lrn: Я попробовал ваш ответ в своей программе, но безуспешно. Фреймворк DI (который оказывается стандартным фреймворком DI DI: pub.dartlang.org/packages/di) попытался внедрить объект _Future и потерпел неудачу. Сообщение об ошибке «Не удалось выполнить server_lib.IsUserLoggedIn» - тип «_Future» не является подтипом типа «DataManager» из «db». - person Greg Sherman; 07.07.2014
comment
Похоже, что платформа di не обрабатывает асинхронно созданные значения. Может быть, поставить об этом просьбу авторов. Очевидно, это пока только прототип (согласно github.com/angular/di.dart) . - person lrn; 08.07.2014
comment
@lrn: Я отказался от этого подхода. Теперь я пытаюсь инициализировать БД в качестве первого шага в попыткеLogin (и всех других вызовах функций). Однако и на этом фронте я столкнулся с несколькими проблемами. Я разместил здесь вопрос на StackOverflow: stackoverflow.com/questions/24678606/ - person Greg Sherman; 10.07.2014