Skip to content

Instantly share code, notes, and snippets.

@rayfix
Forked from koher/async-await-fitting-to-swift.md
Last active May 9, 2017 09:42
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rayfix/b5429a061a0fe34fa9326fe358dfdbe7 to your computer and use it in GitHub Desktop.
Save rayfix/b5429a061a0fe34fa9326fe358dfdbe7 to your computer and use it in GitHub Desktop.

I propose the addition of async / await to handle asynchronous operations in a manner that is consistent with Swift.

Handling asynchronous operations is an area of great interest and async / await are often referred to in swift-evolution. After some thought, I realized that if you consider async and await as analogous to throws and try, a solution that fits quite well with Swift emerges.

For example, functions calling functions marked with throws must themselves be marked with throws or handle the error explicitly using do / catch. In the same way, functions which call async functions must themselves be async or handle the asynchrony explicitly using do / wait. Just like calling a throwing function requires it to be annotated with try, calling an async function it to be annotated with await.

    // An `async` function
    func downloadData(from url: URL) async -> Data { ... }
    
    // can be used as following

    func downloadFoo(from url: URL) async -> Foo { // Must be `async`
        let data = await downloadData(from: url) // Needs `await`
        return try! JSONDecoder().decode(Foo.self, from: data)
    }

    // or

    func downloadFoo(from url: URL) -> Foo { // Without `async`
        do {
            let data = await downloadData(from: url) // Needs `await`
            return try! JSONDecoder().decode(Foo.self, from: data)
        } wait // Blocking
    }

async / await and throws / try can be used together for asynchronous failable operations.

    // An `async` and `throws` function
    func downloadData(from url: URL) async throws -> Data { ... }

    // can be used as following

    func downloadFoo(from url: URL) async throws -> Foo { // Must be `async` and `throws`
        let data = await try downloadData(from: url) // Needs Both `await` and `try`
        return try JSONDecoder().decode(Foo.self, from: data)
    }
    
    // or

    func downloadFoo(from url: URL) async -> Foo { // Can be only `async`
        do {
            let data = await try downloadData(from: url) // Needs Both `await` and `try`
            return try JSONDecoder().decode(Foo.self, from: data)
        } catch _ {
            return Foo()
        } // Without `wait`
    }

    // or

    func downloadFoo(from url: URL) throws -> Foo { // Can be only `throws`
        do {
            let data = await try downloadData(from: url) // Needs Both `await` and `try`
            return try JSONDecoder().decode(Foo.self, from: data)
        } wait // Without `catch`
    }
    
    // or
    
    func downloadFoo(from url: URL) -> Foo { // Without `async` or `throws`
        do {
            let data = await try downloadData(from: url) // Needs Both `await` and `try`
            return try JSONDecoder().decode(Foo.self, from: data)
        } catch _ {
            return Foo()
        } wait
    }

Also it is possible to think about a reasync which is analogous to rethrows.

    func map<T>(_ transform: (Element) async throws -> T) reasync rethrows -> [T] { ... }

    // behaves as an `async` function only when a received closure is `async`

    let urls = [...]
    let foos = await try urls.map { await try downloadFoo($0) }

async and await can work with GCD, OperationQueue, Thread and so on. To accomplish this, the standard library could provide an asynchronize function like below.

    func asynchronize(_ operation: ((T) throws -> ()) -> ()) async rethrows -> T { ... }
    
    // Implements an `async` function by GCD
    func asyncAfter<T>(deadline: DispatchTime, execute: () -> T) async -> T {
        return asynchronize { resolve in
            DispatchQueue.main.asyncAfter(deadline: deadline) {
                resolve(execute())
            }
        }
    }

The following nonblock function would also be useful to make it easy to realize nonblocking operations.

    func nonblock(_ operation: () async -> ()) { ... }
    
    // Implements nonblocking operations
    @IBAction func onPressButton() {
        nonblock { // This closure is `() async -> ()`
            do {
                let data = await try downloadData(from: url)
                return try JSONDecoder().decode(Foo.self, from: data)
            } catch _ {
                return Foo()
            } // No `wait` here and it makes this closure `async`
        }
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment