Promises: advanced

In the previous article we discussed about the basics of promises: what are and how can be used. Today I will bring into discussion the most powerful feature of promises: chaining.

But first I just want to take a short detour and mention that as the standard for promises emerged on the Javascript platform, it allows completion handlers to be very flexible when it comes to adding success/failure callbacks, in terms of the arguments they expect, and the data they return. Thus the same promise allows callbacks with different signatures (not that you can’t run into problems when the callbacks are executed, but that’s another story).

Now, back to chaining. Thanks to the very well designed specifications of Promises/A+ (I strongly recommend you to read them if you haven’t yet so) , you can achieve at least the following if you use chained promises:

  1. sync-ish coding style when dealing with consecutive async operations that depend one upon another
  2. data transformation from one promise to another
  3. graceful recover from a failed operation, or the other way around marking the chain as failed if the operation result is not the desired one

Before we dive into the three above features, let’s recap: a promise only needs to provide 3 methods – a then() one for adding callbacks, a resolve() one if we want to mark the promise as succeeded and a reject() one if we want to mark the promise as failed. That’s all in terms of the Promise class interface.

The promises great advantage comes from its implementation: then() returns another promise, which gets resolved/rejected based on the callback return value. Calling then()  two times results in two additional promises being dispatched, which be either resolved or rejected based on a combination of the original promise resolution and the value returned by the callback. The specs describe exactly what should happen in every possible combination.

Let’s consider the example from the first article on promises, where adding a book involves 3 calls to the server API: validation of book fields, adding the book which returns it’s ID, fetching the other server generated fields. Having the then() method behaviour in mind, we know that each time it gets called, a new promise is dispatched, promise that continues the chain. This allows us to either continue the chain if the current async operation succeeded, or recover from an error and continue the chain with another operation in case of failure. Isn’t this great?

This leads to another advantage of promise usage, the fact that you can specify only one failure handler, at the end of the chain, and be sure that if any promise in the chain fails, then the failure handler will be executed. Let’s revisit the code from the article:

AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
NSDictionary *bookData = @{@"name": @"Harry Potter"};
[manager POST:@"http://myserver.com/validator/book"
    parameters:bookData].promise.success(^{
    return [manager POST:@"http://myserver.com/book"
              parameters:bookData].promise;

}).success(^(NSString *bookId){
    return [manager GET:[@"http://myserver.com/book/" stringByAppendingString:bookId]
             parameters:nil].promise;

}).success(^(NSDictionary *bookDetails){
    // inform the user that the book was added, move to the appropriate screen

}).failure(^(NSError *error) {
    if([error.domain isEqual:NSURLErrorDomain]) {
        // inform the user that there was a server communication problem
    } else {
        // inform the user about the problem
        // error.domain/error.code can be used to identify which one of the
        // three operations failed
    }
});

As you can see, there’s only one error handler, which gets executed if any of the three async operations fails. This means in the first place no code redundancy, as we will not have to add an error handler for each individual operation.

Another major advantage of promises if the fact that they allow you to transform data. Just look at the following code:

- (CKPromise*)getUserWithId:(NSString*)userId {
    AFHTTPRequestOperationManager *mgr = [AFHTTPRequestOperationManager manager];
    NSString *url = [@"http://myserver.com/user/" stringByAppendingString:userId];
    return [mgr GET:url].promise.success(^(NSDictionary *userInfo) {
        return [[MYUser alloc] initWithDictionary:userInfo];
    });
}

//how to use it later in code:
[SomeUserManagerClass getUserWithId:@"aUserId"].then(^(MYUser *user) {
    // I received the user, yay
}, ^(NSError *err) {
    // some error occured
}

Basically, the Promises/A+ specs tell that if the promise callback (either success or failure) returns a value, then the promise created as a result will be resolved with that value. As I said before, any callback method called on a promise will return a new promise, that will get resolved with the result returned by the callback. This is very helpful as you can gradually transform data from a raw form into some concrete object.

If we think about it, we can have a promise that returns a raw NSString JSON, one that parses the NSString into a NSDictionary, and one that initialises an object from that dictionary. All in one place, all offering the support for async execution. Isn’t this also great?

[myAPIClient GET:@"http://someServer.com/user/1234"].success(^(NSString *response) {
    return [MYJSONParser parseString:response];
}).success(^(NSDictionary *dict) {
    return [[MYUser alloc] initWithDict:dict];
}).failure(^(NSError *error) {
    if([error.domain isEqual:NSURLErrorDomain]) {
        // there was an error communicating with the server
    } else if([error.domain isEqual:MYJSONParserErrorDomain]) {
        // the server sent an invalid JSON
    } else {
        // there only one possible error left
        // the MYUser initialisation failed for some reason
    }
});

Exceptions also have their place in promises specs. Basically if a promise callback throws an exception, then the promise created when adding the callback will be rejected with the thrown exception as reason. Having said this, I think promises cover all possible scenarios when it comes to async operations.

So far so good, however there is a catch. Javascript, for which the original promises were designed, is a loosely typed language. It doesn’t care if you pass one or three params to the callback, it doesn’t care about the types of those params, it doesn’t complain if you return a value or not in those callbacks. How this concept can be implemented for strongly typed languages, like a language from the C family, where every function must be clearly defined, and you cannot have the same method sometimes return something and sometimes return void?

The good news is that at least in Objective-C the promises as defined by the A+ specifications can be implemented. How exactly? I’ll try to describe in the next article, where I will present CKPromise in details.

Did this article answered some of your questions? Did it raise another ones? You can use the comment form if you have something to ask.

Update: You can check the last episode of the series here: http://blog.cristik.com/2015/03/a-promise-implementation-for-objective-c-ckpromise/

Leave a comment

Your email address will not be published. Required fields are marked *