How to use "and_then" with Task

Hi, just learning Ice and really enjoying it so far.

I’m trying to run some tokio tasks, one after the other. From what I can gather from the docs, I’d need to run something like Task::perform together with and_then for the second future, but I can’t really figure out how to do this, since Task::perform takes a second argument, a closure. So, for instance, I can’t really do this (I don’t think):

let f1: Future<Result<i32>>;
let f2: Future<Result<i32>>;

Task::perform(f1).and_then(f2);

I can, of course, just create an async function that runs f1 and f2 in sequence, but I was just wondering how to use and_then for that.

Thanks!

Task::future(f1).then(|x| Task::future(f2(x)).map(Message::SomeVariantForResult) might be what you’re looking for.

An illustrative example is below:

use iced::widget::{button, center, column, row, text};
use iced::{Center, Element, Fill, Shrink, Task};
use std::time::{SystemTime, UNIX_EPOCH};

pub fn main() -> iced::Result {
    iced::application("iced • futures", App::update, App::view)
        .centered()
        .run_with(App::new)
}

#[derive(Debug, Default)]
struct App {
    input: u64,
    output: Option<u64>,
}

#[derive(Debug, Clone)]
enum Message {
    Generate,
    Generated(u64),
    Calculate,
    Calculated(u64),
    Chain,
}

impl App {
    fn new() -> (Self, Task<Message>) {
        (Self::default(), Task::done(Message::Generate))
    }

    fn update(&mut self, message: Message) -> Task<Message> {
        match message {
            Message::Generate => Task::future(generate()).map(Message::Generated),
            Message::Calculate => Task::perform(calculate(self.input), Message::Calculated),
            Message::Generated(ts) => {
                self.input = ts;
                self.output = None;
                Task::none()
            }
            Message::Calculated(result) => {
                self.output = Some(result);
                Task::none()
            }
            Message::Chain => Task::future(generate())
                .then(|x| Task::future(calculate(x)))
                .map(Message::Calculated),
        }
    }

    fn view(&self) -> Element<Message> {
        let input = text(format!("Input: {:?}", self.input))
            .size(20)
            .center()
            .width(Fill);
        let output = text(
            self.output
                .map(|x| format!("Output: {:?}", x))
                .unwrap_or_default(),
        )
        .size(20)
        .center()
        .width(Fill);

        center(
            column![
                row![
                    button("Future 1").on_press(Message::Generate).width(150),
                    "(generates new input value and stores it)"
                ]
                .align_y(Center)
                .spacing(10),
                row![
                    button("Future 2").on_press(Message::Calculate).width(150),
                    "(calculates the square of the input value)"
                ]
                .align_y(Center)
                .spacing(10),
                row![
                    button("Chained").on_press(Message::Chain).width(150),
                    "(generates value and maps to square directly without storing input)"
                ]
                .align_y(Center)
                .spacing(10),
                input,
                output
            ]
            .spacing(20)
            .padding(20)
            .width(Shrink),
        )
        .into()
    }
}

async fn generate() -> u64 {
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_millis() as u64
        % 50
        + 1
}

async fn calculate(n: u64) -> u64 {
    n * n
}

There are many other ways to work with Tasks: Task in iced - Rust

Ah, perfect! Thank you, @airstrike !!

1 Like