是否Future/java promise future模式 能实现的FRP都能更好的实现

Vert.x3异步框架实战
发表于 16:15|
来源《程序员》电子刊|
作者刘小溪
摘要:作为异步无阻塞的网络框架,Vert.x的参照物是Node.js,基本可以完成Node.js能完成的所有事情。它是作者目前见过功能最强大,依赖第三方库最少的Java框架。本文将带你深入了解Vert.x。
Vert.x的由来Vert.x诞生于2011年,当时叫node.x,不过后来因为某些原因改名位Vert.x。经过三年多的发展,现在已经到了3.2版本,社区也越来越活跃,在最新的官网Vertx.io上,作者用一句话介绍了它,JVM上的Reative开发套件。Vert.x目前是见过最功能最强大,第三方库依赖最少的Java框架,它只依赖Netty4以及Jacskon,另外如果你需要建立分布式的Vert.x则再依赖HazelCast这个分布式框架,注意Vert.x3必须基于Java8。由于基于JVM,所以Vert.x可以用其他语言来实现你的业务。默认官方维护的语言是Groovy,JavaScript以及 JRuby。Vert.x是一个异步无阻塞的网络框架,其参照物是node.js。基本上node.js能干的事情,Vert.x都能干。Vert.x利用Netty4的EventLoop来做单线程的事件循环,所以跑在Vert.x上的业务不能做CPU密集型的运算,这样会导致整个线程被阻塞。图1是一个简单的通过Vert.x起HTTP服务的例子(Java实现)。你可以从官方找到其他语言实现。图1
Vert.x实现HTTP服务刚才上面提到了Vert.x的分布式,Vert.x与node.js有一个很大不同点,在于Vert.x支持分布式,与多核利用。通过Hazelcast管理各个Vert.x节点的信息,然后通过EventBus在节点之间互相发消息,于此同时Vert.x还能支持应用的高可用,只需简单的在启动时加参数-ha即可。具体的可以去官网查看一下用法。下面是Vert.x提供的核心API。HTTP/HTTPS Server/ClientWebsocket SockJSTCP/SSL Server/ClientUDP / DNSFiles / TimerJson / Buffer / Flow ControlEventBus ( 集群 )Distribution (Lock, Map, Counter)Vert.x的执行单元叫verticle。即程序的入口,每个语言可能实现的方式不一样,比如Java需要继承一个AbstractVerticle抽象类,而javascript则直接require(“vertx”)就可以了。verticle分两种,一种是基于EventLoop的适合I/O密集型的,还有一种是适合CPU密集型的worker verticle。而verticle之间相互通信只能通过Eventbus,可以支持point to point 的通信,也可以支持publish & subscribe通信方式。我们重点说一下基于EventLoop的verticle。这个本质上是跟node.js一样的。下面的图其实就是node.js的翻版。图2
node.js的翻版所有业务逻辑其实都会跑在Netty里的EventLoop上,而EventLoop通过循环事件队列来执行所有的业务逻辑,这样可以把一些I/O操作频繁的事件及时从CPU上剥离开来,最后通过注册一个回调Handler来处理所有的事件回调。另外一种worker verticle。主要是用来处理同步处理的。比如第三方框架没有异步接口,最典型就是JDBC。所以可以通过worker verticle来退化到传统的基于多线程模型的实现。这也是匹配一些原项目的手段。图3是Vert.x的内部整体架构图3
Vert.x的内部整体架构大家可以看到,我们的业务逻辑其实都是基于verticle来实现的,然后Vert.x框架会将你的verticle绑定到相关的线程模型上,这里verticle1,verticle2是I/O密集型项目,所有的逻辑都会跑在NIO Worker上。而Verticle3会有一些同步的耗时的请求,则会被绑定到Worker线程模型上。另外两个Vert.x节点则通过EventBus互相通信,而EventBus通过HazelCast来获取整个集群里的节点信息。注意这里每一个verticle其实都是一个线程(启动的时候指定实例数目参数即可),这样可以充分的利用多核。而node.js其实只能通过Cluster来提升多核利用。Vert.x的部署场景及开发痛点图4是一个典型的Vert.x部署场景。图4
Vert.x部署场景我们会把逻辑拆成小的verticle。这里你可以把这些小的verticle看成是微服务,然后水平扩展这些服务,同时也可以把自己的业务按CPU密集与I/O密集型拆分。服务与服务之间可以通过EventBus互相调用,另外Vert.x的EventBus调用目标verticle的时候会按RoundRobin算法来做balance。我们来看看Vert.x开发的痛点,这其实是所有异步开发都会遇到的痛点,就是Callback Hell。因为你所有的业务逻辑都会被拆成一个个不连贯的代码块,也就是说一个业务逻辑如果涉及到I/O操作你必须要通过回调接口来继续完成,这样就丢失了局部变量,而且异常捕获也会变得非常麻烦。图5是一个Callback Hell的例子图5
Hell代码示例这里代码的含义是通过EventBus给service-address1发送一个消息,然后等待返回后再把结果发送给service-address2,再等待service-address2的返回结果发送给service-address3。这里形成了调用链,即下一步的行为依赖上一步的返回结果。这个在前端用Ajax的同学肯定很熟悉。那解决办法呢,node.js里是用promise,而Vert.x可以使用Java8自带的CompletableFuture来实现同样的效果。图6就是用CompletableFuture改写的例子图6
Hell代码示例大家可以发现代码变得更扁平了,没有那么多的嵌套,然后通过一些介词比如,then, when等来组合各个异步的业务逻辑,最后在一个地方统一的捕获异常。这里大量用了Java8的新功能,比如Lambda表达式,如果觉得奇怪的同学,建议先去熟悉一下Java8的Lambda表达式。图7
CompletableFuture代码示例那有没有更好的实现方式了呢,能不能变成同步方式呢。这里Vert.x提供了一个库vertx-sync可以实现Fiber。通过Fiber来防止线程Block,从而将异步代码完全的变成同步代码。这里代码瞬间变得非常清晰,完全是同步的样式。vertx-sync其实是依赖了quasar这个Java库,它通过修改Java字节码来实现相关的逻辑,这里其实是在EventLoop线程里又开辟了一个线程池,所有的在EventLoop里的同步的方法会被这个线程池接管,处理完后会再返回给EventLoop线程。这样可以避免EventLoop线程被阻塞。图8
vertx-sync代码示例但是个人不推荐在生产环境使用这个库,因为它毕竟不是语言级别的支持Fiber。需要JVM启动的时候通过javaAgent来加载相关的quasar库。这里还有一个库,在今天特别的火——RxJava。这个其实是Reactive的Java实现,官方也提供了相关的支持,但是Reactive比较复杂,除非的业务涉及很多的流式操作,否则不建议你使用。下面是官方的一个例子。简单说明一下,这里定义了一个EventBus,用来接受发给heat-sensor的消息,然后每隔1秒钟对累积的消息进行一次批处理,这里通过Java8的Stream接口做了一次求平均值,最后将结果通过EventBus发给news-feed这个verticle做进一步的处理。这里大家可以发现RxJava可以做更多的事情,前提是大家要对FRP编程思想能够接受。Vert.x3常用工具最后在提一下几个Vert.x3的一些小工具。一个是metrics。这个可以用来统计整个Vert.x内部的一些指标信息,比如HTTP请求数,TCP接受或者发送的流量等等,具体可以看官方文档,通过这个接口我们可以实时的统计Vert.x内部性能信息。另外Vert.x提供了专门针对异步代码的单元测试框架vertx-test-unit。通过redeploy这个参数可以动态的热部署整个verticle,这个对开发调试时非常有用。最重要的是Vert.x3内置了EventLoopChecker这个动态监测所有EventLoop线程的工具,默认EventLoop被阻塞了2秒钟的时候会触发报警,如果持续阻塞则会直接打印那一块的异常栈到日志里,非常方便开发者来检查自己的异步代码。Vert.x目前在国内还不是很火,但是在国外已经有很多企业在使用了,比较注明的比如英孚教育、Hulu、以及做JVM监控的一家公司jClarity等。作者简介刘小溪, 高级开发工程师。Vert.x社区一员,贡献vert.x的Clojure实现,同时扩展了Vert.x的分布式实现。目前在推进基于Vert.x的微服务化,容器化相关技术。本文选自程序员电子版2015年12月B刊,该期更多文章请查看。2000年创刊至今所有文章目录请查看。欢迎(含iPad版、Android版)。
推荐阅读相关主题:
为了更好帮助企业深入了解国内外最新大数据技术,掌握更多行业大数据实践经验,进一步推进大数据技术创新、行业应用和人才培养,-12日,由中国计算机学会(CCF)主办,CCF大数据专家委员会承办,中国科学院计算技术研究所、北京中科天玑科技有限公司及CSDN共同协办的2015中国大数据技术大会(Big Data Technology Conference 2015,BDTC 2015)将在北京新云南皇冠假日酒店隆重举办。
相关热门文章[英] 在 Swift 中实现 Future/Promise 模式 - 推酷
[英] 在 Swift 中实现 Future/Promise 模式
Back to the Futures
a video from the
The asynchronous code we’re used to writing in Objective-C has a lot of problems. It’s not great for handling errors, and it doesn’t scale well when you find yourself composing many asynchronous functions to create new asynchronous functions. In this talk,
demonstrates how we can implement a Future type in Swift that can greatly simplify our asynchronous APIs. Building on a Result type, we go via map and andThen , ending on a suggestion that, for Swift, the future of Futures might be signals, and ReactiveCocoa 3.
To see the code used in this talk, Javi has made a Playground, which you can view on
— we won’t email you for any other reason, ever.
Doing Things Better with Swift(0:00)
Thank you everyone. I’m super happy to be up here talking to you guys about Swift. Thanks a lot to the organizers for putting this together and having me here. My name is Javi. I go by
on Twitter, and I’m an iOS engineer at Twitter.
There are a lot of things that we can do better in Swift than we could do before in Objective-C. We’ve already seen a couple examples of those in the previous talks. And when I continue with that train, I’m going to talk about one design pattern that I really like that we can bring to Swift. It will make our code a lot better.
Asynchronous APIs Today(1:19)
As Chris Eidhof mentioned, I’m also going to avoid saying words that start with “M”, and I’m going to keep it really practical. We’re going to see how we traditionally do asynchronous APIs, and we’re going to see a couple of problems with that approach, especially in regards to error handling. Then I’ll introduce a simple Future API, we’ll see how we could implement that, and how it helps improve our asynchronous code.
Let’s talk about the problem. What does this code look like today?
struct User { let avatarURL: NSURL }
func requestUserInfo(userID: String, completion: (User?, NSError?) -& ())
func downloadImage(URL: NSURL, completion: (UIImage?, NSError?) -& ())
func loadAvatar(userID: String, completion: (UIImage?, NSError?) -& ()) {
requestUserInfo(userID) { user, error in
if let user = user {
downloadImage(user.avatarURL) { avatar, error in
if let avatar = avatar { completion(avatar, nil) }
else { completion(nil, error!) }
} else { completion(nil, error!) }
This is Swift code that’s written in the same way we write Objective-C asynchronous APIs. There is a bunch of nested callbacks. There is a load avatar function that, given the userID string, tries to download an avatar. You pass the completion block that will be called with an image or an error, and this function doesn’t really have any logic. It doesn’t know how to do it, so what it composes itself with smaller functions that also do other things asynchronously. It uses the requestUserInfo function to retrieve the user asynchronously, and the download image function. The way it does that is by nesting those calls within each other. Every time there is an error it has to check explicitly, build out and call the completion block.
This may look fine to most of you who are very used to this, but this pattern doesn’t really scale. It’s very easy to end up here, and I’m sure you have seen this, even if you don’t do PHP. So let’s avoid it, we can do better. However, there are some other even more specific problems with this code. One of the asynchronous APIs that takes a completion block like this will look at the types. The completion block takes a tuple, they’re both optional because it’s possible that the download fails, or it’s possible that it doesn’t fail and then there is no error.
Those question marks you see, what is important to understand is that they’ve always been there , we’ve just never seen them. They were implicitly always there in Objective-C, because any point or type could carry a nil value.
If we look at all the possible combinations that can come in that Optional, we find that there are two. There are the ones that we expect: either there is an image and no error, or no image and an error. What’s funny is that there are another two cases that are totally possible, and don’t really make a lot of sense. What is my app supposed to do with an image and an error, what should I make out of that? And what if I don’t have an image but I don’t have an error either? What happens?
The Importance of Handling Errors(3:42)
I always like to say how I see computers as perfect machines that never make any mistakes. And whenever we encounter bugs, there has been a difference between what the programmer thought they told the computer to do, and what they actually told the computer to do. Where we don’t have a great way to express what we want to accomplish, we’re more likely to tell the computer to do the wrong thing.
I think, particularly in regards to error handling, this is very important. We need to handle errors appropriately in our applications. This sort of API lets you misuse it in many ways, for example, checking the error first as opposed to checking the value first. That’s an anti-pattern. Foundation tells you you should do it the other way around, in case there’s an error but it was actually a success.
I Hate NSError(4:55)
And then there is NSError . I hate NSError , but I can’t really blame Apple because, to be honest, it’s the best that you can do in Objective-C. Let’s see one example of why NSError is problematic.
var error: NSError?
let string = NSString(contentsOfFile:path encoding:NSUTF8Encoding error:&error)
if string == nil {
if error.code == NSFileReadNoSuchFileError {
else if ...
This code is wrong, but it’s not immediately obvious at all. We’re calling a Foundation API. If we look at the documentation, it makes absolutely no reference to what kinds of errors we can find inside the error that we get in return. It doesn’t mention the domain, and it doesn’t mention the error codes. What we’re doing here is checking the error code without first checking the domain, and that doesn’t really make sense. There can be two error codes with the same value in different domains, we would be making the wrong assumption here. Another thing that we can do with this API is pass nil in the error parameter, and just tell Foundation, “I don’t care about errors”, but I think that robust software requires us to take really good care of errors.
It’s not surprising that in the software that we use every day we find crazy things like this, and we can’t really blame developers. If our tools don’t make it easy and convenient to handle errors, we’re just going to be lazy. We’re going to pass nil to that method. We’re going to ignore the errors. We’re not going to be able to know which errors are there, so we’re just going to bring them to the console and move on.
A Proposal(6:40)
This is what I propose: Instead of using NSError , we can use our own specific types that encapsulate the types of errors that we can encounter in our APIs. We can create a protocol that all those types can conform to and participate in being errors.
protocol ErrorType { }
enum UserInfoErrorDomain: ErrorType {
case UserDoesNotExist
case UserRequestFailure(reason: String)
case NetworkRequestFailure(reason: String)
extension NSError: ErrorType { }
For example, if we have an API that does some sort of thing with users, we can declare an enum that conforms to this protocol and has three very clear error cases. Perhaps it can provide even more additional information, like a reason string — we’ll see in a second how we’ll use that. If we really want to use NSError , we can also make NSError participate in this.
Another really cool thing that we can do with this, and it may seem contradictory at first, is that we could even create a NoError type.
enum NoError: ErrorType { }
let error = NoError(?)
Weird, right? The cool thing about this type is that, because it’s declared as an empty enum, we actually cannot create a value of that type, because it doesn’t have any constructor. So now you say that’s even more useless. But precisely for the reason that we know that null values can be constructed. If we have an API that tells us the type of errors that it’s going to carry, is type NoError , we know that the API is fallible, it can fail. What’s important about this is that we actually don’t need a unit test to prove it, the compiler can verify that for us.
Building on a Result Type with Future(8:22)
We’re going to build on top of a Result type, as we saw in Brian’s talk earlier. Result can represent either a value, in the case of a success or an error — but not both things at the same time — or neither. I’m cheating a litt This is currently not possible in Swift because of compiler limitation. You have to make use of a workaround like the Box class. But I chose to eliminate it from the slides because it’s kind of noisy. It’s not really important and it reads better this way.
enum Result&T, E: ErrorType& {
case Success(T)
case Error(E)
So let’s look at Future . How can we implement this? Hopefully by looking at how the implementation works we’ll understand the value, and it’s also a lot of fun! So what’s Future ? You may have heard of them with another name perhaps in some other programming languages or frameworks. They’re known as Promises. They’re essentially the same thing.
struct Future&T, E: ErrorType& {
typealias ResultType = Result&T, E&
typealias Completion = ResultType -& ()
typealias AsyncOperation = Completion -& ()
private let operation: AsyncOperation
Futures encapsulate different computation, which is a very fancy way of saying that they abstract the work required to retrieve some value that
for example, a network latency or something like that. But, you could say, “Oh, that’s just like closures, right?”, or, “That’s just like callbacks!”. But the great thing about Futures is that we can make an API that allows us to treat those Futures as if they were the actual values that they carry. So we can manipulate them, we can transform them, we can concatenate them, and we eliminate all the noise related to the asynchrony itself. That simplifies our code a lot .
Implementing Future (10:16)
Let’s start implementing it. We can create a struct, and the struct is generic over T and E like Result , because it’s essentially going to use Result . I have defined a couple of type aliases to help us with the types. Result type is what we just saw, and we’re going to have a completion type that’s going to be invoked when you tell Future , “Now okay, give me the value”. You’re going to pass the completion block so it takes the Result type and doesn’t return anything. The asynchronous incorporation is the closure that will be called to start the work to return the value, so that’s going to be called with a completion block that it will call when it’s done, and it doesn’t return anything. Future encapsulates the operation, and we can instantiate it by passing that operation. The important bit of the public API is that start method, that’s what allows the client of the API to tell Future , “Okay now retrieve the value and give it to me when it’s done.” But so far it is not that useful. It looks a whole lot like completion blocks except for the use of Result .
struct Future&T, E: ErrorType& {
init(operation: AsyncOperation) {
self.operation = operation
func start(completion: Completion) {
self.operation() { result in
completion(result)
map & andThen (11:15)
What’s really cool about Future is when we implement some operators on them. I want to implement map and andThen , so let’s go with map first. This is what we would accomplish with a map function. We have a user ID string and we want to get the URL for the avatar. We create a function that returns that, and we have another function already that downloads the user information and gives us this really simple user struct, which is a URL. If we want to transform one into the other instead of nesting callbacks, we can just map it and transform it by returning the other material from inside the user.
struct User { let avatarURL: NSURL }
func requestUserInfo(userID: String) -& Future&User, ErrorDomain&
func requestUserAvatarURL(userID: String) -& Future&NSURL, ErrorDomain& {
return requestUserInfo(userID)
.map { $0.avatarURL }
This is what the type signature looks like. I think it’s interesting to compare it to the map function signature, and, for example, all their two types in the Standard Swift Library, Array and Optional. If we look at the types, they are exactly the same. They’ll take a function that knows how to get a value of type U giving a value of type T . Which T is the type of values inside that container, and U will be the type of the values in the new container.
struct Array&T& {
func map&U&(f: T -& U) -& [U]
enum Optional&T& {
func map&U&(f: T -& U) -& U?
struct Future&T, E: ErrorType& {
func map&U&(f: T -& U) -& Future&U, E&
Implementing map on Future (12:44)
So let’s implement it. We can create a new Future because it has to be of type U , so we need a new Future . This is how we substantiate it, like we saw, passing an operation. In order to transform the value, we first need to get the value, so we call start and we get the result. This result can either be a success or a failure, so we need to switch on it.
func map&U&(f: T -& U) -& Future&U, E& {
return Future&U, E&(operation: { completion in
self.start { result in
switch result {
case .Success(let value):
completion(Result.Success(f(value)))
case .Error(let error):
completion(Result.Error(error))
Let’s go over the success case. The success case, like the type signature, tells us we need to get the value transformed with the F function — we’ll run it again in a success case and we’ll call the completion block. The error case is even simpler. We don’t have a value, we can’t transform it, we just need to call the completion block with the same error. This is what it looks like in one slide.
This short circuiting that happens in the error cases essentially is the same that Brian showed with the Railway Oriented Programming concept. But now let’s say that we want to do something with the value that the initial Future gives us, but we want to retrieve a new value given that. Retrieving that value is not as easy as calling the avatar URL. Retrieving that value also incurs some delay, we need another network request, for example.
Declaring andThen (13:48)
This is not going to work because we need to pass a function that returns another Future. So we can declare a Future and an andThen function on our Future API. We would use it like this, for example: If we have a request user avatar URL like we just saw, we can now return the Future of your image by concatenating the download image operation after that. We just call andThen and pass the other function.
func requestUserAvatarURL(userID: String) -& Future&NSURL, ErrorDomain&
func downloadImage(URL: NSURL) -& Future&UIImage, ErrorDomain&
func downloadUserAvatar(userID: String) -& Future&UIImage, ErrorDomain& {
return requestUserAvatarURL(userID)
.andThen(downloadImage)
This is what the signature looks like. Similar to map , the important difference is that, like I said, the F function doesn’t return a U . It returns a new Future. Implementing it is very, very similar. Again we create a Future, we started the initial one to retrieve the value, and we switch on the result. On the success case now, when we call F on the value we don’t have a value that we can return. Now we have another Future, so we need to start it, and when we’re done that’s the final result.
In the error case, the same thing, we have to short circuit it. If we don’t have a value, we can’t transform it to continue doing more operations with it. We need to call the completion block immediately. It is the whole implementation, and you’ll realize that it looks a lot like map . In fact it’s interesting that you can implement one in terms of the other, but I chose to implement them separately to see what they have to do.
So let’s see what we’ve accomplished. We went from a nested callback mess that has to explicitly handle errors in every single call, to a declarative function that tells us exactly what we need to do to retrieve this avatar: We need to request user info, map it into the avatar URL, and then download the image. It’s not about lines of code here, it’s about our ability to express our intentions in the code. But the great benefit of this as well is that, if you look at the function on the bottom, there is no error handling. All the errors get carried through automatically, as soon as they happen.
func andThen&U&(f: T -& Future&U, E&) -& Future&U, E& {
return Future&U, E&(operation: { completion in
self.start { firstFutureResult in
switch firstFutureResult {
case .Success(let value): f(value).start(completion)
case .Error(let error): completion(Result.Error(error))
Future Limitations(16:45)
So hopefully you’ll like Futures as much as I do and they’ll seem as useful as I think they are. But it’s important to understand a few limitations of them. The example that we saw is very common in our mobile applications. We have a network request, some sort of asynchronous request like that. But that’s not the only type of asynchronous thing that we deal with. And Futures failed to represent all their abstractions that require sending more than one value through. This Future can only retrieve one value, but sometimes we deal with things like user interactions, like a gesture recognizer, that will send many different values over time.
Another difference with other APIs that we deal with is that they’re not consumer-driven like the one that we just saw. You don’t tell the gesture recognizer, “Okay, now recognize the touch.” It’s the user touching the screen. It’s the producer pushing values onto the screen.
Signals(18:08)
The good news is that there’s a better abstraction in Futures. Signals allow you to represent all of those things. Signals are a very powerful tool, they’re in a way a superset of Future, and therefore they’re a very, very useful abstraction.
The next great thing is that we don’t have to implement them. ReactiveCocoa, which you may have heard of implements signals and allows you to build your app using this abstraction. ReactiveCocoa 3 is written in Swift, and you can check it out on
. But Colin, right after me, is going to talk to you guys more about that.
The slides may go a little fast but I made a playground with all the code available on my
repository. I encourage you guys to download it, play with it.
And with that I’m happy to take questions.
Thank you everyone.
Q&A(19:05)
Q: Why did you decide to implement Future with both the type and the error implementing the NoError class, instead of allowing the programmer to do a Future operation? You could implement Future, which takes only one generic type, and then that generic type can be Result , so you don’t have to specify NoError as the type of the error.
Javi: That’s a good question. The thing with that is that it’s because we use Result that the implementations of map and andThen know how to short circuit errors, right. So if we make it a generic and we make it work with things that are not Result , map and andThen are not going to know what’s a failure and what’s a success. Does that make sense?
Q: Why did you call it andThen instead of flatMap ?
Javi: That’s a good question. Some of you may realize that the indent function is also known as flatMap . That’s true. The reason why I called it andThen is because I think it expresses better the meaning and what we’re using it for. flatMap is now in the Swift Standard Library so I think we’ll start seeing it more and more, but I wanted to introduce the fewest functional programming terms in this talk, so that’s why I decided not to use it.
— we won’t email you for any other reason, ever.
已发表评论数()
请填写推刊名
描述不能大于100个字符!
权限设置: 公开
仅自己可见
正文不准确
标题不准确
排版有问题
主题不准确
没有分页内容
图片无法显示
视频无法显示
与原文不一致}

我要回帖

更多关于 netty future promise 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信