我们调用的是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,
Self: Sized,
{
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。