As an async programming pattern, Future has been popular with many of our programmers across a wide range of languages. Loosely speaking, Future is a wrapper around a value which will be available at some point in the future. Strictly speaking, Future is a monad which supports the following 3 operations:

unit :: T -> Future<T>
map :: (T -> R) -> (Future<T> -> Future<R>)
flatMap :: (T -> Future<R>) -> (Future<T> -> Future<R>)

When holding a future, we know the type of the value, we can register callbacks which will be called when the future is done. But callbacks are not the recommended way to deal with futures, the point of Future pattern is to avoid callbacks and in favor of future transformation. By properly using future transformation, we can make our async code look like sequential code, the callbacks are hidden from us by the futures.

Here is an example, say there are 2 async RPCs. One takes a user ID and returns a future of a list of the user's new message header (ID and title), the other takes a message ID and returns its body.

// RPC 1: Gets a list of new message (headers) of a user.
Future<NewMessagesResponse> getNewMessages(UserId userId); // RPC 2: Gets the full message for a message ID.
Future<Message> getMessage(MessageId messageId); // Data structures.
class Message {
class Header {
MessageId id;
String title;
}
class Body {
...
} Header header;
Body body;
} class NewMessagesResponse {
List<MessageHeaders> headers;
}

Your task is that, given a user ID and a keyword, get the user's new messages whose title contains the keyword. With future transformation, the code may look like:

// Gets the future of a list of messages for a user, whose titles contains a given keyword.
Future<List<Message>> getNewMessages(UserId userId, String keyword) {
Future<NewMessagesResponse> newMessagesFuture = getNewMessages(userId);
Future<List<MessageId>> interestingIdsFuture = filter(newMessagesFuture, keyword);
Future<List<Message>> messagesFuture = getMessages(interestingIdsFuture);
Return messages;
}

The structure of the code is similar to what we do with synchronous code:

List<Message> getNewMessages(UserId userId, String keyword) {
NewMessagesResponse newMessages = getNewMessages(userId);
List<MessageId> interestingIds = filter(newMessages, keyword);
List<Message> messages = getMessages(interestingIds);
Return messages;
}

The async and sync functions are isomorphic, there is a correspondence in their code structure. But their runtime behaviors are different, one happens asynchronously, one happens synchronously.

Now here comes the real challenge. What if we change the RPC a bit, say there may be too many new messages that it has to return messages page by page, each response may contain an optional next page token indicating there are more pages.

// RPC 1: Gets one page of the new message (headers) of a user. The page number is denoted by a pageToken.
Future<NewMessagesResponse> getNewMessageHeaders(UserId userId, String pageToken) class NewMessagesResponse {
List<MessageHeaders> messageHeaders;
String nextPageToken; // Non-empty nextPageToken indicates there are more pages.
}

Your task remains the same, write a function which takes a user ID and a keyword, return a list of the user's new messages whose titles contain the keyword.

Future<List<Message>> getNewMessages(UserId userId, String keyword) {
//TODO
}

The difficulty lies with that in regular future transformations we have fixed number of steps, we can simply chain them together sequentially, then we get one future of the final result; but now because of pagination, the number of steps is not nondeterministic, how can we chain them together?

For synchronous code, we may use a loop like:

List<Message> getNewMessages(UserId userId, String keyword) {
List<MessageId> interestingMessages = new ArrayList<>();
String pageToken = "";
do {
NewMessagesResponse newMessages = getNewMessages(userId, pageToken);
List<MessageId> interestingIds = filter(newMessages, keyword);
allNewMessages.addAll(newMessages.headers);
pageToken = newMessages.nextPageToken;
} while (!isEmpty(pageToken));
}

But unfortunately loop is applicable to futures. How can we get one future for all the pages? Recursion comes to rescue. This is what I call *Stateful Future Transformation*.

class State {
UserId userId;
String keyword;
int pageIndex;
String pageToken;
List<MessageId> buffer;
} Future<State> getInterestingMessages(Future<State> stateFuture) {
return Future.transform(
stateFuture, (State state) -> {
if (state.pageIndex == 0 || !isEmpty(state.pageToken)) {
// Final state.
return Future.immediate(state);
} else {
// Intermediate state.
Future<NewMessagesResponse> newMessagesFuture =
getNewMessages(state.userId, state.pageToken);
return Future.transform(newMessagesFuture, newMessages -> {
state.pageIndex++;
state.pageToken = newMessages.nextPageToken;
state.buffer.addAll(filter(newMessages, state.keyword);
});
}
});
} Future<State> getInterestingMessages(UserId userId, String keyword) {
State initialState = new State(userId, keyword, 0, "", new ArrayList());
Future<State> initialStateFuture = Future.immediate(initialState);
return getInterestingMessages(initialStateFuture);
}

The code above can be refactored into a general stateful future transformation function:

// Transforms the future of an initial state future into the future of its final state.
Future<StateT> transform(
Future<StateT> stateFuture,
Function<StateT, Boolean> isFinalState,
Function<StateT, Future<StateT>> getNextState) {
return Future.transform(
stateFuture,
(StateT state) -> {
return isFinalState.apply(state)
? Future.immediate(state)
: transform(getNextState.appy(state));
}
});
}

Stateful Future Transformation的更多相关文章

  1. Isomorphic JavaScript: The Future of Web Apps

    Isomorphic JavaScript: The Future of Web Apps At Airbnb, we’ve learned a lot over the past few years ...

  2. Spark Streaming揭秘 Day24 Transformation和action图解

    Spark Streaming揭秘 Day24 Transformation和action图解 今天我们进入SparkStreaming的数据处理,谈一下两个重要的操作Transfromation和a ...

  3. Future Works on P4

    Future Works on P4 P4 and NV: MPvisor, Hyper4, HyperV, Flex4 P4 and NFV P4 and Network Cache P4 and ...

  4. explain the past and guide the future 好的代码的标准:解释过去,指引未来;

    好的代码的标准:解释过去,指引未来: Design philosophies | Django documentation | Django https://docs.djangoproject.co ...

  5. .Netcore 2.0 Ocelot Api网关教程(10)- Headers Transformation

    本文介绍Ocelot中的请求头传递(Headers Transformation),其可以改变上游request传递给下游/下游response传递给上游的header. 1.修改ValuesCont ...

  6. 使用 Vert.X Future/Promise 编写异步代码

    Future 和 Promise 是 Vert.X 4.0中的重要角色,贯穿了整个 Vert.X 框架.掌握 Future/Promise 的用法,是用好 Vert.X.编写高质量异步代码的基础.本文 ...

  7. 面向未来的友好设计:Future Friendly

    一年前翻译了本文的一部分,最近终于翻译完成.虽然此设计思想的提出已经好几年了,但是还是觉得应该在国内推广一下,让大家知道“内容策略”,“移动优先”,“响应式设计”,“原子设计”等设计思想和技术的根源. ...

  8. 线程笔记:Future模式

    线程技术可以让我们的程序同时做多件事情,线程的工作模式有很多,常见的一种模式就是处理网站的并发,今天我来说说线程另一种很常见的模式,这个模式和前端里的ajax类似:浏览器一个主线程执行javascri ...

  9. 第二篇 Entity Framework Plus 之 Query Future

    从性能的角度出发,能够减少 增,删,改,查,跟数据库打交道次数,肯定是对性能会有所提升的(这里单纯是数据库部分). 今天主要怎样减少Entity Framework查询跟数据库打交道的次数,来提高查询 ...

随机推荐

  1. 51单片机学习笔记(郭天祥版)(9)——IIC、EEPROM

    IIC是两根线,单总线,只有一根数据线,发送数据和读取收据都是一根线,像我们之前学的AD.DA都是许多线,许多线的话,这样做系统可以少浪费资源,少浪费控制IO口的资源,这种并行的处理速度快.所以线越多 ...

  2. GlusterFS

    1.      GlusterFS概述 GlusterFS是Scale-Out存储解决方案Gluster的核心,它是一个开源的分布式文件系统,具有强大的横向扩展能力,通过扩展能够支持数PB存储容量和处 ...

  3. python词频统计及其效能分析

    1) 博客开头给出自己的基本信息,格式建议如下: 学号2017****7128 姓名:肖文秀 词频统计及其效能分析仓库:https://gitee.com/aichenxi/word_frequenc ...

  4. stm32定时器时钟以及中间对齐模式

    在永磁同步电机的控制中,需要对电机的三相定子施加一定的电压,才能控制电机转动.现在用的较多的是SVPWM(SVPWM的具体原理会在后面另写一篇博客说明),要想产生SVPWM波形,需要控制的三相电压呈如 ...

  5. 深入理解HashMap和CurrentHashMap

    原文链接:https://segmentfault.com/a/1190000015726870 前言 Map 这样的 Key Value 在软件开发中是非常经典的结构,常用于在内存中存放数据. 本篇 ...

  6. JS练习第三课

    用typeof查看数据类型 <pre> <script type="text/javascript"> alert(typeof 12345); <s ...

  7. [转]vue跨域解决方法

      vue跨域解决方法 vue项目中,前端与后台进行数据请求或者提交的时候,如果后台没有设置跨域,前端本地调试代码的时候就会报“No 'Access-Control-Allow-Origin' hea ...

  8. 对DOM,SAX,JDOM,DOM4J四种方法解析XML文件的分析

    1.DOM 与平台无关的官方解析方式 DOM是一次性把xml文件加载到内存中,形成一个节点树 对内存有要求 2.SAX java提供的基于事件驱动的解析方式 每次遇到一个标签,会触发相应的事件方法 3 ...

  9. 什么是JavaScript原型

    JS 原型 转载自[EC前端 - JavaScript原型] 原型是JavaScript最重要的概念.同时也是初级开发者最忌惮的内容,原因在于网上很少有关于它的合理描述. 但事实上,原型很简单,你可以 ...

  10. 上传文件,经过Zuul,中文文件名乱码

    问题描述: 在学习<SpingCloud与Docker微服务架构实战>8.7节 使用Zuul上传文件,测试通过Zuul上传中文文件时出现,文件名.目录名或卷标语法不正确异常:但是直接通过上 ...