【译】理解Rust中的Futures(二) (5)

我们调用的是try_poll而不是poll。

值得注意的是,当你像这样来链接组合子的时候,它们的类型前面可能会变得很长且在编译错误信息中难以阅读。and_then函数要求 future 调用时的错误类型和由闭包返回的类型必须是相同的。

MapErr

回到我们的想象的 api 调用。假定调用的 api 都返回带有同一类错误的 future,但是你需要在调用之间进行额外的步骤。假定你必须解析第一个 api 结果然后把它传递给第二个。

// 无法编译
fn main() {
    let files_future = get_user(1)
        .and_then(|user_string| parse::<User>())
        .and_then(|user| get_files_for_user(user));

    match block_on(files_future) {
        Ok(files) => println!("User Files: {}", files),
        Err(err) => println!("There was an error: {}", err),:w
    };
}

这看起来很好,但是无法编译,并且会有个晦涩的错误信息说它期望得到像ApiError的东西但是却找到了一个ParseError。你可以在解析返回的Result上使用过map_err组合子,但是对于 future 应该如何处理呢?如果我们为 TryFuture 实现一个map_err,那么我们可以重写成下面这样:

// 无法编译
fn main() {
    let files_future = get_user(1)
        .map_err(|e| format!("Api Error: {}", e))
        .and_then(|user_string| parse::<User>())
        .map_err(|e| format!("Parse Error: {}", e))
        .and_then(|user| get_files_for_user(user))
        .map_err(|e| format!("Api Error: {}", e));

    match block_on(files_future) {
        Ok(files) => println!("User Files: {}", files),
        Err(err) => println!("There was an error: {}", err),:w
    };
}

如果这让你看着比较混乱,请继续关注本系列的第三部分,我将谈谈如何处理这个问题和你可能会在使用 future 时遇到的其他问题。

下面是我们实现map_err的方式

mod future {
    pub trait TryFuture {
        // ...

        fn map_err<E, F>(self, f: F) -> MapErr<Self, F>
        where
            F: FnOnce(Self::Error) -> E,
            SelfSized,
        {
            MapErr {
                future: self,
                f: Some(f),
            }
        }
    }

    // ...
    pub struct MapErr<Fut, F> {
        future: Fut,
        f: Option<F>,
    }

    impl<Fut, F, E> Future for MapErr<Fut, F>
    where
        Fut: TryFuture,
        F: FnOnce(Fut::Error) -> E,
    {
        type Output = Result<Fut::Ok, E>;

        fn poll(&mut self, cx: &Context) -> Poll<Self::Output> {
            match self.future.try_poll(cx) {
                Poll::Ready(result) => {
                    let f = self.f.take().unwrap();
                    Poll::Ready(result.map_err(f))
                }
                Poll::Pending => Poll::Pending,
            }
        }
    }
}

fn main() {
    let my_future = future::ready(1)
        .map(|val| val + 1)
        .then(|val| future::ready(val + 1))
        .map(Ok)
        .and_then(|val| future::ready(Ok(val + 1)))
        .map_err(|_: ()| 5);

    println!("Output: {:?}", block_on(my_future));
}

Playground 链接[12]

唯一比较陌生的地方是Poll::Ready(result.map_err(f))。在这段代码里,我们传递我们的闭包到Result类型的map_err函数里。

包装 (Wrap Up)

现在,文章开头的代码可以运行了!比较酷的是这些全都是我们自己实现的。还有很多其他用途的组合子,但是它们几乎都是相同的方式构建的。读者可以自己练习一下,试试实现一个map_ok组合子,行为类似于TryFuture上的map_err但是适用于成功的结果。

Playground 链接[13]

概要重述(Recap)

Rust 中的 Future 之所以如此强大,是因为有一套可以用于链接计算和异步调用的组合子。

我们也学习了 Rust 强大的函数指针 trait,FnOnce,FnMut和Fn。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zypjwd.html