Async 概览

可能你已经注意到Vapor中的一些API会期望或返回一个通用的Future类型。对于第一次听说futures的人起初会存在些困惑但别担心,Vapor会让它们变得很易用。

Promises 和 futures是有关联的,但也是截然不同的类型。Promises 用来创建futures。通常,你将使用Vapor的APIs所返回的future来工作从而不必担心创建promises。

type description mutability methods
Future 引用一个可能还是不可用的对象 只读 .map(to:_:) .flatMap(to:_:) do(_:) catch(_:)
Promise 提供一些异步的对象 可读写 succeed(_:) fail(_:)

Future是基于回调的异步API的替代品,Futures可以链式以及转化来使用,这是简单闭包无法企及的方式,这使得Futures非常强大。

Transforming

就像Swift中的可选项,futures可以被mapped以及flat-mapped。这些是你在futures上执行的最常见操作

method signature description
map to: U.Type, _: (T) -> U 把一个future Map成一个不同的值
flatMap to: U.Type, _: (T) -> Future<U> 把一个future Map成一个不同的future值
transform to: U 将一个future Map成一个已经可用值

如果你查看Optional<T>Array <T>上的mapflatMap的方法签名,您将看到它们与Future <T>上的方法非常相似

Map

.map(to:_ :)方法允许你将future's value 转换为另一个value。由于future's value 可能还不可用(这个值可能是一个异步任务的结果),所以我们必须提供一个闭包来接受值。

/// 设想我们从某个API获取了一个future string
let futureString: Future<String> = ...

/// 将这个 future string转为 integer
let futureInt = futureString.map(to: Int.self) { string in
    print(string) // 实际的string
    return Int(string) ?? 0
}

/// 现在我们有了一个 future integer
print(futureInt) // Future<Int>

Flat Map

.flatMap(to:_:)方法允许你将future's value 转化为另一个future's value。它以"flat"命名是因为他可以让你避免创建嵌套的futures(比如FUture<Future<T>>).换句话说,它可以帮助你保持展平的通用futures.

/// 假设我们从某个API获取一个 Future<String> 对象
let futureString: Future<String> = ...

/// 假设我们创建了一个http客户端
let client: Client = ... 

/// Flat-map 操作将Future<String>转为Future<Response>
let futureResponse = futureString.flatMap(to: Response.self) { string in
    return client.get(string) // Future<Response>
/// 现在我们就有了一个Future<Response>
print(futureResponse) // Future<Response>

信息

如果我们在上面的例子中使用了.map(to:_:),那么我们就会获取一个Future<Future<Response>>类型的结果,哎呀!

Transform

.transform(_ :)方法允许你修改future's value,而忽略现有值。这对于转化那些对于future的实际价值并不重要的Future <Void>类型的结果特别有用。

提示

Future<Void>,有时被称为信号,其唯一目的是通知你一些异步操作的完成或失败。

/// 假设我们从某个API获取了一个 future<void>
let userDidSave: Future<Void> = ...

/// 将其转化为一个http状态
let futureStatus = userDidSave.transform(to: HTTPStatus.ok)
print(futureStatus) // Future<HTTPStatus>

这个操作仍然是一个转变,即使我们已经提供了一个可使用的值用来 transform.在之前的futures还未完成(或者失败)之前,这个future不会完成。

Chaining

关于futures转化的重要部分是它们可以通过链式调用。这使你可以轻松表达许多转换和子任务。 让我们修改上面的例子,看看我们如何利用链式。

/// 假设我们获取一个future返回值从某个api
let futureString: Future<String> = ...
/// 假设我们已经初始化了一个客户端
let client: Client = ... 

/// 先转化为一个URL再转化为一个Response类型。
let futureResponse = futureString.map(to: URL.self) { string in
    guard let url = URL(string: string) else {
        throw Abort(.badRequest, reason: "Invalid URL string: \(string)")
    }
    return url
}.flatMap(to: Response.self) { url in
    return client.get(url)
}
print(futureResponse) //Future<Response>

初次调用Map之后,会创建一个临时Future <URL>类型,这个future对象会被立刻通过flat-map操作转为一个Future<Response>类型。

提示

你可以在一个flat-map或者一个map的闭包内抛出一个错误,这将会使这个future失败并且抛出这个错误。

Future

让我们来看看Future 上一些不常用的方法

Do / Catch

与Swift的do / catch语法类似,futures也有一种do和catch方法来等待未来的结果。

/// Assume we get a future string back from some API
let futureString: Future<String> = ...
futureString.do { string in
    print(string) // 实际值
}.catch { error in
    print(error) // 一个Swift类型的错误
}

提示

.do.catch一起工作。如果你忘记.catch,编译器会警告你一个未使用的结果。不要忘记处理错误的情况!

Always

你可以使用always来添加一个无论是失败还是成功都会被调用的回调。

/// Assume we get a future string back from some API
let futureString: Future<String> = ...
futureString.always {
    print("The future is complete!")
}

笔记

你可以根据需求为future添加尽可能多的回调

Wait

你可以使用.wait()同步等待future完成。由于future可能会失败,所以这个调用会抛出异常。

 /// Assume we get a future string back from some API
let futureString: Future<String> = ...
/// 一直阻塞到string操作完成
let string = try futureString.wait()
print(string) /// String

警告

不要在一个vapor 的路由或者控制器中使用这个方法,阅读Blocking来获取更多信息。

Promise

大多数情况下,你是通过调用vapor的API's来获取一个需要操作的future。但是,在某些时候,你可能需要创建自己的promise。

要创建promise,你需要访问EventLoop。Vapor中的所有容器都有一个可以使用的eventLoop属性。最常见的是,就是当前的Request

/// Create a new promise for some string
let promiseString = req.eventLoop.newPromise(String.self)
print(promiseString) // Promise<String>
print(promiseString.futureResult) // Future<String>
/// Completes the associated future
promiseString.succeed(result: "Hello")
/// Fails the associated future
promiseString.fail(error: ...)

信息

一个Promise对象只能够被完成一次,所有的后续完成操作都会被忽略。

线程安全

Promises可以从任何线程完成(succeed(result:) / fail(error:))。这就是为什么promise需要一个事件循环来初始化。Promise确保完成操作在它的事件循环执行。

Event Loop (事件循环)

它通常会为其运行的CPU中的每个内核创建一个事件循环在你的应用程序启动时。每个事件循环只有一个线程。如果你熟悉Node.js的事件循环,就会发现Vapor中的事件循环与Node.js中的非常相似。唯一的区别是Vapor可以在一个进程中运行多个事件循环,因为Swift支持多线程。

每次客户端连接到你的服务器时,它都将被分配给其中一个事件循环。从这一点开始,服务器和该客户端之间的所有通信都将发生在同一个事件循环(并关联到这个事件循环的线程)上。

事件循环负责跟踪每个连接的客户端的状态。如果有来自客户端的请求等待读取,则事件循环触发读取通知,导致数据被读取。一旦读取完整个请求,任何等待该请求数据的futures都将完成。

Worker

有权访问事件循环的事物称为Worker。Vapor的每个容器都是一个worker。Vapor中最常用的容器是:

  • Application
  • Request
  • Response

你可以在这些容器上使用.eventLoop属性来访问事件循环。

print(app.eventLoop) // EventLoop

Vapor中有许多方法需要传递当前worker。它通常会有像on: Worker这样的标签。如果你处于路由闭包或控制器中,请传递当前RequestResponse。如果你在启动应用程序时需要一个worker,请使用Application.

Blocking(阻塞)

绝对关键的规则如下

危险

切勿直接在事件循环中进行阻塞调用。

一个阻塞调用的例子,类似于libc.sleep(_ :).

router.get("hello") { req in
    /// 使当前事件循环阻塞5s
    sleep(5)

    ///当线程重新工作室返回一个简单字符串
    return "Hello, world!"
}

sleep(_ :)是一个用于在提供的秒数内阻止当前线程的命令。如果你直接在事件循环中阻塞工作,则在阻塞工作期间,事件循环将无法响应分配给它的任何其他客户端。换句话说,如果你在事件循环中进行睡眠(5),则连接到该事件循环的所有其他客户端(可能为数百或数千个)将被延迟至少5秒

确保只有在后台才运行任何阻塞工作。当这项工作以非阻塞方式完成时,使用Promise通知事件循环。

router.get("hello") { req in
    /// 创建一个空的promise
    let promise = req.eventLoop.newPromise(Void.self)

    /// 在后台线程执行
    DispatchQueue.global() {
        /// 使后台线程睡眠
        /// 这不会影响到当前的事件循环
        sleep(5)

        /// 当阻塞工作完成时
        /// promise和他关联的future会被完成
        promise.succeed()
    }

    /// 等待 future完成
    /// 将结果转为一个简单的字符
    return promise.futureResult.transform(to: "Hello, world!")

}

并非所有的阻塞调用会像sleep(:)如此明显,当你怀疑你调用的方法可能被阻塞时,研究下这个方法本身或者询问相关的人。如果该函数正在执行磁盘或者网络IO并且使用的是一个同步API(无回调或者futures),那么就会发生阻塞的情况。

信息

如果阻塞工作是应用程序的核心部分,则应该考虑使用BlockingIOThreadPool来控制你创建的阻塞工作的线程数。当阻塞工作正在被完成时,这将避免你的事件循环长期获取不到 CPU 时间片。。

results matching ""

    No results matching ""