join!

Макрос futures::join позволяет дождаться завершения нескольких разных футур при одновременном их выполнении.

join!

При выполнении нескольких асинхронных операций возникает соблазн просто последовательно вызвать несколько .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. В некоторых других языках, футуры выполняются до завершения, поэтому две операции могут быть запущены одновременно сначала вызовом каждой 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)
}
}

Однако футуры на Rust не будут работать, пока для них не будет вызван .await. Это означает, что оба приведённых выше фрагмента кода запустят book_future и music_future последовательно, вместо того, чтобы запустить их параллельно. Чтобы правильно распараллелить их выполнение, используйте 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!

Для футур, которые возвращают Result, может использоваться try_join!, а не join!. Так как join! завершается только после завершения всех подфутур, он будет продолжать обрабатывать другие футуры даже после того, как одна из подфутур вернёт Err.

В отличие отjoin!, try_join! завершится немедленно, если какая-либо из подфутур вернёт ошибку.


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

Обратите внимание, что все футуры, переданные в 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)
}
}