join!
Макрос futures::join
позволяет дождаться завершения нескольких разных
футур при одновременном их выполнении.
join!
При выполнении нескольких асинхронных операций возникает соблазн просто
последовательно вызвать несколько .await
:
#![allow(unused)] fn main() { async fn get_book_and_music() -> (Book, Music) { let book = get_book().await; let music = get_music().await; (book, music) } }
Однако это будет медленнее, чем необходимо, так как он не начнёт пытаться выполнять
get_music
до завершения get_book
. В некоторых других языках,
футуры выполняются до завершения, поэтому две операции могут быть запущены
одновременно сначала вызовом каждой async fn
, для запуска футур, а потом их ожиданием:
#![allow(unused)] fn main() { // WRONG -- don't do this async fn get_book_and_music() -> (Book, Music) { let book_future = get_book(); let music_future = get_music(); (book_future.await, music_future.await) } }
Однако футуры на Rust не будут работать, пока для них не будет вызван .await
.
Это означает, что оба приведённых выше фрагмента кода запустят
book_future
и music_future
последовательно, вместо того, чтобы запустить их
параллельно. Чтобы правильно распараллелить их выполнение, используйте
futures::join!
:
#![allow(unused)] fn main() { use futures::join; async fn get_book_and_music() -> (Book, Music) { let book_fut = get_book(); let music_fut = get_music(); join!(book_fut, music_fut) } }
Значение, возвращаемое join!
- это кортеж, содержащий выходные данные каждой из переданных Future
.
try_join!
Для футур, которые возвращают Result
, может использоваться try_join!
, а не
join!
. Так как join!
завершается только после завершения всех подфутур,
он будет продолжать обрабатывать другие футуры даже после того, как одна из подфутур вернёт Err
.
В отличие отjoin!
, try_join!
завершится
немедленно, если какая-либо из подфутур вернёт ошибку.
#![allow(unused)] fn main() { use futures::try_join; async fn get_book() -> Result<Book, String> { /* ... */ Ok(Book) } async fn get_music() -> Result<Music, String> { /* ... */ Ok(Music) } async fn get_book_and_music() -> Result<(Book, Music), String> { let book_fut = get_book(); let music_fut = get_music(); try_join!(book_fut, music_fut) } }
Обратите внимание, что все футуры, переданные в try_join!
, должны иметь один и тот же тип ошибки.
Рассмотрите возможность использования функций .map_err(|e| ...)
и .err_into()
из
futures::future::TryFutureExt
для приведения типов ошибок к единому виду:
#![allow(unused)] fn main() { use futures::{ future::TryFutureExt, try_join, }; async fn get_book() -> Result<Book, ()> { /* ... */ Ok(Book) } async fn get_music() -> Result<Music, String> { /* ... */ Ok(Music) } async fn get_book_and_music() -> Result<(Book, Music), String> { let book_fut = get_book().map_err(|()| "Unable to get book".to_string()); let music_fut = get_music(); try_join!(book_fut, music_fut) } }