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