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