Страницы

Поиск по вопросам

воскресенье, 9 февраля 2020 г.

Scala. Список футур. Выполнение шаг за шагом

#scala #future


Хола, коллеги!
У меня есть список некоторых сущностей. Они могут быть вложены друг в друга. Например:

case class Entity(id: Long, parentId: Long)

val list = List(
    Entity(1,0),
    Entity(2,1),
    Entity(3,2),
    Entity(4,1),
    Entity(5,0)
)


Мне нужно добавить их в БД. Мой сервис:

val listOfFutures = list map createEntity // createEntity - мой метод,
                                          // который добавляет сущность в БД.
                                          // Возвращает Future[Entity]
Future.sequence( listOfFutures ) map { _ => println("Ok!") }


Проблема в constraints(Как их по-русски назвать?). Получаю ошибку:


  Create failed. No such parent 1 entity exists.


Я думаю, что проблема в том, что метод createEntity выполняется параллельно. Как
заставить этот код выполняться последовательно, сущность за сущностью?
    


Ответы

Ответ 1



Future считается монадой, так как у неё есть последовательный flatMap: list.foldLeft(Future.successful(()))(( prevF, entity) => prevF .flatMap( _ => createEntity(entity)) .map( _ => ()) ) Аналогичный код, используя for-comprehension: list.foldLeft(Future.successful(()))(( prevF, entity) => for { _ <- prevF _ <- createEntity(entity) } yield () ) Объяснение: val entity = Entity(0, 1) createEntity(entity).map( _ => println("Ok!")) Метод map запоминает полученную функцию и выполняет её после того как получит успешный результат Future. Meтод flatMap работает также, только ожидает что полученная функция тоже будет возвращать Future. createEntity(entityOne).flatMap( _ => createEntity(entityTwo)) Таким образом мы соединяем две Future последовательно, т.е. вторая вызовется только после успешного выполнения первой. Отсюда очевидно что мы можем выстраивать длинные последовательные цепочки. createEntity(entityOne) .flatMap( _ => createEntity(entityTwo)) .flatMap( _ => createEntity(entityThree)) .flatMap( _ => createEntity(entityFour)) А результатом будет новая Future. Ок, а как поступить если у нас есть список Future? Так же как мы бы поступили бы например с числами. val listNumbers = List(1, 2, 3, 4, 5) val zeroNumber = 0 def joinNumbers(a: Int, b: Int): Int = a + b listNumbers.foldLeft(zeroNumber)(joinNumbers) Почему foldLeft, а не map? Потому что прибавляем по очереди, и нам нужна сумма предыдущих чисел. Зачем нужен zeroNumber? Ну а вдруг список пустой - вернем 0. Теперь с Future: val listEntities: List[Entity] = ??? val zeroFuture = Future.successful(()) def joinFuture(prevF: Future[Unit], entity: Entity): Future[Unit] = prevF .flatMap(_ => createEntity(entity)) // тут вторая Future .map( _ => ()) listEntities.foldLeft(zeroFuture)(joinFuture) Следующий createEntity выполняется после предыдущих. Зачем нужен zeroFuture - ну а вдруг список пустой. UPD: Попробую подробней расписать почему foldLeft, а не map. Сначала взглянем на map: val listOfFutures = list.map( entity => createEntity(entity)) map просто проходит по листу и вызывает создание Энтити. Он не ждёт пока создание завершится, он просто создаёт новую Future и идёт дальше к следующему элементу листа. Т.е. на каждой итерации нам нужно "нечто" что будет знать о том что на предыдущем шаге Энтити создался: val listOfFutures = list.map( entity => val prevEntityWasCreated = ??? // что-то, что знает о предыдущем Энтити // flatMap заставляет выполнятся ПОСЛЕ prevEntityWasCreated.flatMap(_ => createEntity(entity)) ) Т.е. проходимся по списку и на каждом этапе ждем пока завершиться предыдущий. Что-то подобное нам надо, верно? Точнее такое: val listOfFutures = list.map( (prevEntityWasCreated, entity) => prevEntityWasCreated.flatMap(_ => createEntity(entity)) ) Супер. Второй энтити ждет когда создастся первый, третий ждёт когда создастся второй. А чего ждёт первый? Надо создать пустую Future специально для первой этнити: val zeroFuture = Future.successful(()) val listOfFutures = list.map(zeroFuture)( (prevEntityWasCreated, entity) => prevEntityWasCreated.flatMap(_ => createEntity(entity)) ) Всё отлично, только тип не совпадает: val zeroFuture: Future[Unit] = Future.successful(()) val foo: Future[Entity] = prevEntityWasCreated.flatMap(_ => createEntity(entity)) Просто добавим map и изменим тип на одинаковый val zeroFuture: Future[Unit] = Future.successful(()) val foo: Future[Unit] = prevEntityWasCreated.flatMap(_ => createEntity(entity)).map( _ => ()) Последнии штрих - переименовать новую функцию в foldLeft. list.foldLeft(Future.successful(()))(( prevF, entity) => val foo: Future[Unit] = prevF .flatMap( _ => createEntity(entity)) .map( _ => ()) // возвращаем Future для следующего элемента foo )

Комментариев нет:

Отправить комментарий