Wrapping your callbacks in Promises

Published on: November 2, 2015

callbacks_promises

A little while ago I wrote a post about PromiseKit. In this post I wrote mainly about how you could wrap API calls in Promises using the NSURLConnection extension that the creator of PromiseKit provides. Since writing that article I've had a bunch of people asking me more about PromiseKit. More specifically, some people wanted to know how they could wrap their existing code in Promises. To illustrate this I'm going to use Parse as an example.

A regular save action in Parse

Before we start making Promises, let's have a look at how you'd normally do something with Parse. An example that any Parse user probably has seen before is one where we save a PFUser.

let user = PFUser()
user.email = email
user.password = password
user.username = email

user.signUpInBackgroundWithBlock {(succeeded: Bool, error: NSError?) -> Void in
    guard error == nil else {
        // handle the error
    }

    // handle the succesful save
}

Above code should be fairly straightfowards. First we create a user, then we call signUpInBackgroundWithBlock and then we specify the block that should be executed after the user has been signed up. A lot of Parse's save and fetch actions follow this pattern where you use callbacks as a means to handle success and error cases. In my previous post I explained that Promises are a cleaner method to deal with asynchronous operations, but wrapping something like signUpInBackgroundWithBlock in a Promise isn't very straightforward.

Creating a custom Promise

Setting up a Promise isn't very hard. What you're going to need to decide on in advance is what your Promise is going to return. After that you're going to need to figure out the conditions for a fulfilled Promise, and the conditions for a rejected Promise. How about a simple example?

func doSomething(willSucceed: Bool) -> Promise {
    return Promise { fulfill, reject in
        if(willSucceed) {
            fulfill("Hello world")
        } else {
            reject(NSError(domain: "Custom Domain", code: 0, userInfo: nil))
        }
    }
}

The function above returns a Promise for a String. When you create a Promise you get a fulfill and reject object. When you create your own Promise it's your responsibility to make sure that you call those objects when appropriate. The doSomething function receives a Bool this flags if the Promise in this example will succeed. In reality this should be dependent on if the action you're trying to perform succeeds. I'll show this in an example a little bit later. If we want this Promise to succeed we call fulfill and pass it a String because that's what our Promise is supposed to return when it's succesful. When the Promise should be rejected we call the reject function and pass it an ErrorType object. In this example I chose to create an NSError.

Now that we have explored a very basic, but not useful example, let's move on to actually wrapping a Parse call in a Promise.

Combining Parse and PromiseKit

When you're doing asynchronous operations in Parse you call functions like 'signUpUserInBackgroundWithBlock'. This function takes a callback that will be executed when the user has signed up for your service. Let's see how we can transform this callback oriented approach to a Promise oriented one.

func signUpUser(withName name: String, email: String, password: String) -> Promise {
    let user = PFUser()
    user.email = email
    user.password = password
    user.username = name

    return Promise { fulfill, reject in
        user.signUpInBackgroundWithBlock { (succeeded: Bool, error: NSError?) -> Void in
            guard error == nil else {
                reject(error)
                return
            }

            fulfill(succeeded)
        }
    }
}

In the example above a function is defined signUpUser, it takes some parameters that are used to set up a PFUser object and it returns a Promise. The next few lines set up the PFUser and then we create (and return) the Promise object. Inside of the Promise's body we call signUpInBackgroundWithBlock on the PFUser. The callback for this function isn't entirely avoidable because it's just how Parse works. So what we can do, is just use the callback to either call reject if there's an error or call fulfill when the operation succeeded.

That's all there is to it, not too complex right? It's basically wrapping the callback inside of a Promise and calling reject or fulfill based on the result of your Parse request. One last snippet to demonstrate how to use this new function? Sure, let's do it!

signUpUser(withName: "Donny", email: "[email protected]", password: "secret").then { succeeded in
    // promise was fulfilled! :D
}.error { error in
    // promise was rejected.. :(
}

The way this works is basically identical to how I described in my previous post. You call the function that returns a Promsie and use the then and error (report in an earlier PromiseKit release) to handle the results of the Promise.

Wrapping up

In this post I've showed you how to take something that takes a callback and wrap it in such a way that it becomes compatible with PromiseKit. The example was about Parse but this principle should apply to just about anything with a callback.

The only thing I didn't cover is where to actually implement this. I did that on purpose because it really depends on your architecture and partially on preference. You could opt to put the wrapping functions in a ViewModel. Or you could create some kind of Singleton mediator. Or maybe, and I think I prefer this method, you could write some extensions on the original objects to provide them with functions like saveUserInBackgroundWithPromise so you can actually call that function on a PFUser instance.

If you'd like to know more, have questions or have feedback, you can find me on Twitter or in the (amazing) iOS Developers Slack community.

Categories

Swift

Subscribe to my newsletter