As you may know, in addition to my love for the Swift language, I also love to work with Nodejs and Javascript. In my last blog post I talk about the ES7 Async functions and why I think it is an amazing feature in order to make the async code more readable and easy to write. I was frustrated to not have these kind of features with the Swift language so I decided to write my own library: AwaitKit. In this post I’ll expose you the philosophy behind AwaitKit
, how to it works and how to use it.
AwaitKit
The AwaitKit
library aims to make the write of asynchronous code more easy by letting write it like synchronous one. Here a little example:
// Write this kind of code
do {
let user = try await(User.logIn(username, password))
let posts = try await(Posts.fetchWithUser(user))
handlePosts(posts)
}
catch {
handleError(error)
}
// Instead of this one
User.logIn(username, password) { user, error in
if let error = error {
self.handleError(error)
}
else {
Posts.fetchWithUser(user) { posts, error in
if let error = error {
self.handleError(error)
}
else {
self.handlePosts(posts)
}
}
}
}
This example illustrates the philosophy of AwaitKit
and how it works by the same time. In the both cases we are trying to log in a user and then to retrieve his blog posts. Of course errors can occurred during the process and we take care of them.
AwaitKit
, in addition of the await
method, also comes with another useful method named async
which executes the given closure in background and returns a promise as result:
func postsForUserWithName(username: String, password: String) -> Promise<[Post]> {
return async {
let user = try await(User.logIn(username, password))
let posts = try await(Posts.fetchWithUser(user))
return posts
}
}
In this example, we are creating a postsForUserWithName
method which will return in a “future” a list of Post
. The main part of this method is the async
closure which will be wrapped into a promise and returns. The advantage here is the async
takes care of exceptions and reject the promise if necessary. To perform promises, AwaitKit
uses PromiseKit.
The promise returned can be used later to manage its result:
let promise = postsForUserWithName("yannickl", password: "awaitkit")
promise.then { posts in
print(posts)
}
.error { error
print(error)
}
If you already understood the powerful of the mechanism, here is a neat alternative:
let posts = try! await(postsForUserWithName("yannickl", password: "awaitkit"))
Under the hood
The main part of the library is to wait for the promise is resolved (or rejected). To do so the code in the async
or await
blocks are executed on a background queue and then we use semaphores to wait for a completion.
Here is the core part:
let semaphore = dispatch_semaphore_create(0)
promise
.then(on: queue) { value -> Void in
dispatch_semaphore_signal(semaphore)
}
.error(on: queue) { err in
dispatch_semaphore_signal(semaphore)
}
dispatch_semaphore_wait(semaphore, UINT64_MAX)
As you can see, the semaphore helps you to block the current thread until the promise resolved or rejected. There is two important points to take into consideration here:
- If you call this code on the main thread it’ll be blocked until the promise did resolved. So you should never call this code on the main thread!
- If the queue passed into parameter of the promise is the same queue of the execution scope you will enter into a deadlock because the thread is blocked with the use of
dispatch_semaphone_wait
and the promise will never be able to resolve.
The other parts of the library mainly focus to wrap blocks into promises.
Examples
In this section we are going to see some example to help you to understand how to use AwaitKit
. First of all let’s define a useful method for our examples:
func request(method: Method, url: URLStringConvertible) -> Promise<NSData> {
return Promise{ resolve, reject in
Alamofire.request(method, url).response { (_, _, data, error) in
if let error = error {
reject(error)
}
else {
resolve(data)
}
}
}
}
We are using Alamofire to make HTTP request, and here we are wrapping the request call into a promise in order to be reusable with AwaitKit
.
Now you can retrieve the content of a url writing this kind of code:
do {
let content = try await(request(.GET, "http://yannickloriot.com"))
print(content)
}
catch {}
// Or you can also use the async block
async {
let content = try await(self.request(.GET, "http://yannickloriot.com"))
print(content)
}
// If you want use the result later
let result = async {
return try await(self.request(.GET, "http://yannickloriot.com"))
}
result.then { content in
print(content)
}
In this example we are investigating three possibilities to use AwaitKit
for the same purpose. In the first case you need to manage yourself the exceptions but it lets you more control and the code is “synchronous” thanks to the await
method. In the second case, we use an async
block which take care of the exceptions but the code is running asynchronous outside its scope. Concerning the last case, you get the promise returned by the async
block and you can use the result later.
If you need more control on which queue the async
or await
run you can pass it into parameter when you call them:
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
async(on: queue) {
let content = try await(on: queue, self.request(.GET, "http://yannickloriot.com"))
}
The last thing, and it may evolve in the time, you can also use directly block code instead of promises with the await
method. The API may change in the future but here an example to allow you to understand its use:
let message = try await {
NSThread.sleepForTimeInterval(2)
return "Eh oh!"
}
print(message) // Display "Eh oh!"
Here the “Eh oh!” message is returned after 2 seconds. But thanks to the await
method everything works like in a synchronous mode and the main thread is not blocked during the 2 seconds of sleep.
Conclusion
This post was an introduction to the AwaitKit
library. It aims to make the write of asynchronous code easier and I believe it does. If there is something you didn’t understand or improvements to do, don’t hesitate to let a comment below or to add an issue or a pull request to the github repo.
Happy coding!