Updated on 02 June 2017

Figure 1: A bridge is something that helps you get from one place of understanding to another.
Figure 1: A bridge is something that helps you get from one place of understanding to another.

In my journey, I crossed the bridge of promises/futures and started construction of a new castle in the enchanted land of reactive extensions. Previous attempts to make the transition from the imperative world were not as successful before that bridge was established. Instead, the traversing of rough waters was required any time I wanted to visit the other side. It was that bridge that allowed the easy back and forth passage between imperative and reactive in the kingdom of Swift (v3.1).

To those familiar with the mind bending struggle in going from ingrained imperative thought processes to the more declarative, reactive side of programming, I can only suggest that promises/futures may be the intermediate stepping stone that will get you across.

Promises provide a subset of the functionality found in programming with reactive extensions (Rx). In many cases, they are all you need to control multiple asynchronous operations. For the case of creating serverless functions for something like AWS Lambda, they can be the perfect tool in Node.js.

A common problem: Get prerequisite data for an asynchronous operation

The problem is how to obtain user data, without using fixed storage like a property, prior to performing an operation with the data?

The pattern using promises is to chain an operation obtaining the needed data to a second operation that uses the data. Order is guaranteed by virtue of the serialization that is inherent to promises. This pattern can be equally implemented using Rx. By showing how this is done, my aim is to bridge your understanding of promises into Rx.

The Promise library I’m using is PromiseKit and the Rx library is RxSwift.

I have a new version of Calendar Timer 2 under development and I’m going to use actual code from my project. Since I’m using a database on Firebase, the code is also going to be tailored to that platform. The general differences between promises and reactive extensions, however, apply to many platforms and languages.

The Promise chaining solution

In the following code, I have generated the solution using a promise chain. I create a new Promise to hold the result of a unique ID that corresponds to a user-generated timed session configuration or SessionConfigID. I first need to get the user’s ID before I can save the data for the user. This is obtained from another Promise (line 10).

Once I have the prerequisite data, I call setValue (line 20) on a Firebase database reference to save the model object for the session configuration. The errors are handled by failing the promise. Upon success, the unique key that is generated using Firebase is returned by the Promise (line 23).

 1 /// Save the session to the database for the current user.
 2 func save() -> Promise<SessionConfigID?>
 3 {
 4     let users   = FirebasePath.users.rawValue
 5     let configs = FirebasePath.sessionConfigs.rawValue
 6     let prefix  = FirebasePrefix.sessionConfigs.rawValue
 7 
 8     return Promise { send, fail in
 9         firstly { () -> Promise<UserID?> in
10             return FirebaseUserStore.getCurrentUserID()
11         }.then { (userID: UserID?) -> () in
12             guard let uwUID = userID else {
13                 fail(FirebaseError.userIDMissingError)
14                 return
15             }
16 
17             let (dref, key) = self.dbRefWithKey(children: [users, uwUID, configs],
18                                                 prefix: prefix)
19 
20             dref.setValue(self.toFirebase(),
21                           withCompletionBlock: { (err, ref) in
22                 if err != nil { fail(err!) }
23                 else { send(key) }
24             })
25         }.catch { err in
26             // The error handling here feels awkward and redundant
27             // but it is required by the promise pattern.
28             fail(err)
29         }
30     }
31 }

With promises, there is really only one way to combine operations and that is in a serial chain. On the other hand, reactive extensions offer a much greater set of options. However, for the purpose of comparison I’m going to limit my example to performing the equivalent operations of a promise chain.

The Observable chaining solution

I create a function that returns an Observable of a user’s sessionconfiguration ID that identifies a custom model object used to storeuser data. I first need the user’s ID to be able to save a user created session configuration. In my code, this is handled by an Observable assigned to uidObs. With reactive extensions, flatMap is the chaining tool that allows you to chain the results of one observableinto another. See Figure 2 for a diagram to help you visualize what happens with observables during a flatMap. Once I have the user ID, I pass it into the Observable that returns the SessionConfigID (line22).

 1 /// Save the session to the database for the current user.
 2 func save() -> Observable<SessionConfigID?>
 3 {
 4     let users   = FirebasePath.users.rawValue
 5     let configs = FirebasePath.sessionConfigs.rawValue
 6     let prefix  = FirebasePrefix.sessionConfigs.rawValue
 7     let uidObs  = FirebaseUserStore.currentUserID()
 8 
 9     return uidObs.flatMap { uid -> Observable<SessionConfigID?> in
10         return Observable.create { observer in
11             guard let uwUID = uid else {
12                 observer.onError(FirebaseError.userIDMissingError)
13                 return Disposables.create()
14             }
15 
16             let (dref, key) = self.dbRefWithKey(children: [users, uwUID, configs],
17                                                 prefix: prefix)
18 
19             dref.setValue(self.toFirebase(),
20                           withCompletionBlock: { (err, ref) in
21                 if err != nil { observer.onError(err!) }
22                 else { observer.onNext(key) }
23             })
24 
25             return Disposables.create()
26         }
27     }
28 }

It can be seen that promise chaining and observable chaining are similar. It’s the small details of how to arrange the code that takes time to be comfortable. With the reactive code, there were many other ways to accomplish the same result but I took special care to express an example that shows how similar they can be.

Figure 2: Modified marble diagram for flatMap.A source observable is mapped and each transformed element ends up in an observable of observables. These are flattened into a single new observable containing the merged, transformed elements.
Figure 2: Modified marble diagram for flatMap. A source observable is mapped and each transformed element ends up in an observable of observables. These are flattened into a single new observable containing the merged, transformed elements.

Conclusion: Observables can chain just as well as Promises

In summary, operation chaining can be accomplished with both promises or observables.

  • If you choose to use promises, you have to make things you want to chain into promises.
  • If you choose to use observables, you have to make things you want to chain into observables.

Since both are just wrappers around operations in your native language, you can wrap one with the other if needed. I’ve found the occasion to wrap an Observable with a Promise while migrating a project from promises to Rx.

The templates for each are illustrated below.

Promise chaining template

firstly { () -> Promise<T> in
    return somePromiseOfTypeT()
}.then { (result: T) -> Promise<U> in
    return somePromiseOfTypeU(use: result)
}.then { (result: U) -> () in
    // Do something with the result of type U.
}.catch { err in
    // Handle the error.
}

Alternatively, if you are willing to accept some rightward creep, the firstly keyword can be dropped.

return somePromiseOfTypeT().then { (result: T) -> Promise<U> in
   return somePromiseOfTypeU()    
}.then { (result: U) -> () in
   // Do something with the result of type U.
}.catch { err in
   // Handle the error.
}

How to handle errors within a promise chain can be problematic. Any error is meant to interrupt execution of the series. The reactive toolset is much more versatile for error handling by being able to handle them where needed. Rx does not force you into a fixed pattern of error handling like the way promises do.

Observable chaining template

someObservableOfTypeT().flatMap { (result: T) -> Observable<U> in
    return someObservableOfTypeU(use: result)
}.subscribe(onNext: { (result: U) in
    // Do something with the result of type U.
}, onError: { err in
    // Handle the error
}).addDisposableTo(bag)

Moving the flatMap operator to the next line with a continuation indent allows maintaining leftward alignment of your operators while emphasizing the source observable. This is a style I prefer with Rx.

someObservableOfTypeT()
    .flatMap { (result: T) -> Observable<U> in
        return someObservableOfTypeU(use: result)
    }.subscribe(onNext: { (result: U) in
        // Do something with the result of type U.
    }, onError: { err in
        // Handle the error
    }).addDisposableTo(bag)

Final words on chaining

Just like with promise chains where the thens can continue on and on, flatMaps can also be added for any number of returned observables. With Rx, however, the difference is this chaining is a situational occurrence. The reactive toolbox has many other options. Note that I have shown you observable chaining in two different contexts, during creation of an observable and during subscription. If you have enjoyed how promises simplify chained asynchronous operations, you will love the extra control that Rx offers for the same underlying operations.

Being on the other side, I find it’s more difficult to get promises to fit every problem as they are less malleable resulting in less options than the reactive tool set. They do fit some problems well, like serverless functions on AWS Lambda in Node.js, but outside of those cases you will battle to get them to do more than linear chaining. While a chain can be the perfect choice, it is unsuitable for the range of complex needs found in more advanced apps. For that, my friends, there is an observable that can be crafted for anything.



blog comments powered by Disqus