Slide 1

Slide 1 text

ASYNCHRONOUS PROGRAMMING

Slide 2

Slide 2 text

WHAT IS ASYNC? > Reactive/Event driven > Promises & Futures > Event loops > Nonblocking > Streams

Slide 3

Slide 3 text

WHY?

Slide 4

Slide 4 text

VAPOR 2 SYNCHRONOUS EVENT LOOPS

Slide 5

Slide 5 text

VAPOR 2 // Creating a new user in a JSON API drop.post("users") { request in // Extract form data guard let json = request.json, let email = json["email"]?.string, let password = json["password"]?.string else { throw Abort.badRequest } // Create a new user let user = try User(email: email, hasingPassword: password) // Success is dependent on successful insertion try user.save() return Response(status: .ok) }

Slide 6

Slide 6 text

VAPOR 3 ASYNCHRONOUS EVENT LOOPS

Slide 7

Slide 7 text

VAPOR 3 /// Type safe registration form struct RegistrationRequest: Decodable { var email: String var password: String } app.post("users") { request in // Extract form data let form = try JSONDecoder().decode(RegistrationRequest.self, from: request.body.data) // Create a new user let user = try User(email: form.email, hasingPassword: form.password) // Success is dependent on successful insertion return try user.save().map { // This transform will be ons successful save return Response(status: .ok) } }

Slide 8

Slide 8 text

VAPOR 3 > Less dependent on external sources > Same amount of code (20 lines) > Much more performant > Much more scalable !

Slide 9

Slide 9 text

AWESOME! HOW DO I USE IT?

Slide 10

Slide 10 text

3 MAIN CONCEPTS > Promise + Future > Streams > Event loop

Slide 11

Slide 11 text

PROMISE & FUTURE

Slide 12

Slide 12 text

THE OBJECTS // Create a promise // Promises are write-only, emitting an event let promise = Promise() // A future can be extracted from a promise // Futures are read-only, receiving the promised event let future: Future = promise.future

Slide 13

Slide 13 text

DELIVERING A PROMISE let promise = Promise() // Complete the promise whenever it's ready promise.complete("Hello world") // Or fail the promise if something went wrong promise.fail(error)

Slide 14

Slide 14 text

RECEIVING THE RESULT let future: Future = promise.future // `then` will be called on success future.then { string in print(string) // `catch` will be called on error }.catch { error in // Handle error }

Slide 15

Slide 15 text

TRANSFORM RESULTS struct UserSession: SessionCookie { var user: Reference } app.get("profile") { request in let session = try request.getSessionCookie() as UserSession // Fetch the user return try session.user.resolve().map { user in // Map the user to a ResponseRepresentable // Views are ResponseRepresentable return try view.make("profile", context: user.profile, for: request) } }

Slide 16

Slide 16 text

NESTED ASYNC OPERATIONS app.get("friends") { request in let session = try request.getSessionCookie() as UserSession let promise = Promise() // Fetch the user try session.user.resolve().then { user in // Returns all the user's friends try user.friends.resolve().then { friends in return try view.make("friends", context: friends, for: request).then { renderedView in promise.complete(renderedView) }.catch(promise.fail) }.catch(promise.fail) }.catch(promise.fail) return promise.future }

Slide 17

Slide 17 text

NESTED ASYNC OPERATIONS app.get("friends") { request in let session = try request.getSessionCookie() as UserSession // Fetch the user return try session.user.resolve().flatMap { user in // Returns all the user's friends return try user.friends.resolve() }.map { friends in // FlatMap replaced this future with return try view.make("friends", context: friends, for: request) } }

Slide 18

Slide 18 text

YOU CAN ADD MANY CALLBACKS let future: Future = generateLogMessage() future.then(print) future.then(log.error) future.map { message in try LogMessage(message).save() }.catch { saveFailure in log.fatal(saveFailure) }

Slide 19

Slide 19 text

SYNCHRONOUS APIS ARE STILL USABLE WHEN WORKING WITH SYNCHRONOUS APIS, YOU CAN BLOCK THE ASYNC OPERATION // Throws an error if the promise failed // Returns the expected result by blocking the thread until completion let result = try future.blockingAwait(timeout: .seconds(5))

Slide 20

Slide 20 text

STREAM An asynchronous sequence of events with 2 primary types InputStream & OutputStream ... and completely protocol oriented

Slide 21

Slide 21 text

OutputStream Emits events of a specific type // TCP Socket outputting data socket.flatMap { byteBuffer in // returns a `String?` // `flatMap` will filter out invalid strings return String(bytes: byteBuffer, encoding: .utf8) // Prints all valid strings // `print` will print the String and return `Void`, creating a Void stream // The stream without data will still be called for each event (such as errors) }.map(print).catch { error in // handle errors }

Slide 22

Slide 22 text

InputStream Receives emitted events of a specific type class PrintStream: InputStream { /// Used to chain errors from stream to stream public var errorStream: ErrorHandler? func inputStream(_ input: String) { print(input) } init() {} } let printStream = PrintStream() // Takes received bytes from the sockets socket.flatMap { // Tries to turn the bytes into a String return String(bytes: byteBuffer, encoding: .utf8) // Drains it into a PrintStream's inputStream }.drain(into: printStream)

Slide 23

Slide 23 text

USE CASE - THE VAPOR HTTP SERVER let server = try TCPServer(port: 8080, worker: Worker(queue: myQueue)) // Servers are a stream of accepted web client connections // Clients are an input and output stream of bytes server.drain { client in let parser = RequestParser() let router = try yourApplication.make(Router.se let serialiser = ResponseSerializer() // Parses client-sent bytes into the RequestParser let requestStream = client.stream(to: parser) // Parses requests to the Vapor router, creating a response let responseStream = requestStream.stream(to: router) // Serializes the responses, creating a byte stream let serializedResponseStream = responseStream.stream(to: serializer) // Drains the serialized responses back into the client socket serializedResponseStream.drain(into: client) }

Slide 24

Slide 24 text

let client = try TCPClient(worker: Worker(queue: myQueue)) try client.connect(hostname: "example.com", port: 80) try client.send(data) let data = try client.read() ❌

Slide 25

Slide 25 text

let client = try TCPClient(worker: Worker(queue: myQueue)) try client.connect(hostname: "example.com", port: 80) try client.writable(queue: myQueue).then { try client.send(data) let data = try client.read() } try client.start() ✅

Slide 26

Slide 26 text

EVENT LOOPS

Slide 27

Slide 27 text

A thread/DispatchQueue of tasks Tasks can be added for an event

Slide 28

Slide 28 text

WORKERS PROVIDE CONTEXT > Share 'globals' such as a database driver > Guaranteed to be the same thread > Require no thread-safety

Slide 29

Slide 29 text

Using a global database driver Using the context's database driver

Slide 30

Slide 30 text

RESULT

Slide 31

Slide 31 text

> Performance > Stability > Easier APIs

Slide 32

Slide 32 text

THE END