join!
Макрос futures::join позволяет дождаться завершения нескольких разных
futures при одновременном их выполнении.
При выполнении нескольких асинхронных операций возникает соблазн просто
сделать несколько .await последовательно:
# #![allow(unused_variables)] #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. В некоторых других языках,
futures выполняются до завершения, поэтому две операции могут быть запущены
одновременно сначала вызовом для каждой async fn, чтобы их запустить, а потом ожиданием обеих:
# #![allow(unused_variables)] #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) } #}
Однако futures на Rust не будут работать, пока для них не будет вызван .await.
Это означает, что оба приведённых выше фрагмента кода запустят
book_future и music_future последовательно, вместо того, чтобы запустить их
одновременно. Чтобы правильно запустить две futures одновременно, используйте
futures::join!:
# #![allow(unused_variables)] #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!
Для futures, которые возвращают Result, рассмотрите возможность использования try_join! а не
join!. Так как join! завершается только после завершения всех subfutures,
он будет продолжать обрабатывать другие futures даже после одного из своих subfutures или вернёт ошибку Err.
В отличие отjoin!, try_join! завершится немедленно, если одна из subfutures
вернёт ошибку.
# #![allow(unused_variables)] #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) } #}
Обратите внимание, что все futures, переданные в try_join!, должны иметь один и тот же тип ошибки.
Рассмотрите возможность использования функций .map_err(|e| ...) и .err_into() из
futures::future::TryFutureExt для консолидации типов ошибок:
# #![allow(unused_variables)] #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) } #}