Rethinking Async

Side note: The extensible syntax thread is actually not about async-await, async-await was just used as an example. Probably not the best move I ever made, the conversation I actually wanted to have never really materialized there. I'm not sure what to do with that now, maybe I should do an extra thread on algebraic effects? Or rename the above one another time to make the topic even clearer?

I like the async/await syntax generally being proposed, modeled off of other languages, but don't find it to be as much of a developer pain point as the amount and the timeline of discussion here suggests. It just seems like a bit of sugar over a simple Future/Promise type that wraps a closure which you can write in 30 lines of Swift.

1 Like

It’s true that there’s not a whole lot you can do with async/await that you can’t do right now with closures. However, as soon as you have even moderately complex data dependencies, closures can become very unwieldy.

For example, here’s a real piece of code I wrote about a year ago using futures with Vapor.

let session = try cookie.flatMap(try req.make(Sessions.self).readSession) ?? req.future(nil)
        
_ = session.unwrap(or: Abort(.unauthorized))    // verify we have a session
    .then { req.authorization($0) }.unwrap(or: Abort(.unauthorized))
    .thenThrowing { (auth: Authorization) -> Authorization in   // and view authorization
        guard auth.permissions == .view else { throw Abort(.unauthorized) }
        return auth
    }.and(req.databaseConnection(to: .questions))
    .then { arg in Question.query(on: arg.1).filter(\.groupID == arg.0.group.id!).all().map { (arg.0, $0) } }    // fetch the questions
    .map {
        let (auth, questions) = $0
        // ..
}

To be fair, it’s not the most well-written code, but that’s because it’s something I hacked together in an afternoon for a short-lived project I’ll never touch again. It should have taken me 30 seconds to write a couple database and session lookups with async/await, but instead I had to fight with nested closures for 20 minutes (with all of the slow compile times and unhelpful error messages involved) until I finally got to something that worked.

With async/await, I could have just done this:

let futureConnection = req.databaseConnection(to: .questions)

// verify we have a session and view authorization
guard 
    let cookie = cookie, 
    let session = await req.make(Sessions.self).readSession(cookie),
    let authorization = await req.authorization(session),
    authorization.permissions == .view else {
        throw Abort(.unauthorized)
}

// fetch the questions
let questions = await Question.query(on: futureConnection).filter(\.groupID == auth.group.id!).all()

// ...

Note how much easier that is both to write and to read. The program structure naturally matches the dataflow, and normal language feature like guard, throw, and local variables just work — even in an asynchronous context.

8 Likes
Terms of Service

Privacy Policy

Cookie Policy