Wrapping your callbacks in Promises
Published on: November 2, 2015A 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.