Are nested closures a good thing

closuresswift-language

I've been doing Swift development for a while now with Firebase, and I find myself making a lot of code like this:

Auth.auth().signIn(with: credential) { (user, error) in
    if let error = error {
        print("an error occurred while signing in!! \(error)")
        return
    }

    print("user is signed in with facebook!")

    FacebookHelper.getProfileInfo(completion: { (name, pictureUrl) in
        FirebaseHelper.setUser(user: user!, name: name, profileUrl: pictureUrl, completion: {
            self.performSegue(withIdentifier: "facebookLoggedInSegue", sender: nil)
        })
    })
}

Is there a better way to approach this? It can just get confusing when I've got stuff like that nested in observe functions.

Best Answer

The ability to have nested functions is a good thing, but obviously any code that is confusing is not a good thing. This kind of code is ubiquitous in the context of asynchronous programming of which your code appears to be an example.

Promises can make it a bit less fugly, but they don't completely solve the problem.

There are two solutions to this. Use threads or use continuations.

It's easy to adapt an asynchronous API to a blocking one using threads and just about any kind of synchronization construct. This is what AwaitKit does. Basically, you just trigger an asynchronous event, and in the completion callback have it signal a semaphore, while you wait on the semaphore in the original thread. E.g. in AwaitKit.

First-class continuations can also be used to solve this problem. You capture the current continuation and pass that as the callback to the asynchronous operation then abandon the current flow of control, e.g. by returning. With just a bit of wrapping, you can make an asynchronous API look and feel like a blocking API with this approach. Few languages support first-class continuations, though. To model them, you can use continuation-passing style (or monadic style for a bit more control), and indeed, that is exactly what these "nested callbacks" are doing.

The async/await syntax popularized by C# was created to solve this problem. Conceptually (but not actually) it performs a local continuation-passing style transform, and then from there it's easy to make an asynchronous API look like a blocking one. (The actual transformation is much more grotty but efficient in the context of C# which doesn't handle nested functions that efficiently.) This is a fairly good solution, but Swift doesn't currently support such syntax.

This blog post which I haven't read completely seems to detail some of the current options in the Swift ecosystem as well as explaining some of the limitations. C# didn't add async/await to the language and compiler for no reason.

Related Topic