Preparing TweetHours for Native Local Notifications


In this video we will be taking a look at the second step in our Commangular sequence.

In this step, we are converting our chosen TweetHours from the format used for the front end display, to the format expected by the Ionic Local Notification service.

As mentioned in the previous video, it really helps if you don't need to do any data manipulation during your command sequences. However, sometimees this is unavoidable.

Commangular Convention

As part of the Commangular command sequence we have the ability to pass the result from the current command step to the next step, by way of the resultKey.

This is what allows us to break up what would otherwise be one large function. We can separate each part of our business requirement into managable steps.

Again, as mentioned in the write up to the previous video, I hadn't anticipated that format expected by the Local Notification service would be that different from the format I was using on the front end.

But it was. And thankfully, adding in a middle / second step to handle, or transform, this data was really not that big of an issue.

Promises, Promises

At this stage in the command sequence we begin to make use of Promises.

Now, promises are a confusing subject and are - in my opinion - best learned through doing.

The best way I have of explaining promises is to compare them to a McDonalds resturant on a busy Saturday afternoon.

Let's say we have a large queue forming at the counter.

Without Promises

Without promises, each person who reaches the front of the queue would place their order, the cashier would take the person's money, then the cashier would go and fulfil the order, holding up the entire queue in the process.

When the order was fulfilled, the cashier would hand the food to the customer, and the customer would leave the queue.

Repeat until the queue is completely empty, and watch as customers become extremely angry in the process.

Of course, this doesn't happen in the real world because it is extremely inefficient to block the queue like this.

With Promises

Back at McDonalds in Promiseland, in order to keep the queue moving, the cashier takes each customer's order. Then the customer leaves the queue and waits for their order to be fulfilled.

This allows the cashier to keep processing orders, entirely handing off the order fulfilment to someone else.

The customer doesn't receive their order immediately. Instead, it is cooked, bagged, and then the customer is called back to the counter where they get their order and then find a seat, leave, or carry out whatever other implementation they have been assigned.

The would be a non-blocking operation. Whilst the orders are cooked, the queue is not blocked.

The customer's receipt can be used as a place holder for their as-yet-unfulfilled order.

When the promised order is resolved, the food is given to the customer.

If the order was for 3 McRibs, but during the preparation the McRibs were accidentally dropped on the floor AND that was the last 3 McRibs available in the store, then the customer would be given the bad news. At this point, the promised order would be rejected, and the customer could make a new order, or go to Burger King or whatever.

How TweetHours uses Promises

During this step in our command sequence, we make use of a Promise to get data back from our API end point.

A promise is useful here as fetching data from a remote location can take an indeterminate amount of time, and could potentially fail.

However, this adds a layer of extra complexity to the process.

Let's look at the code specifically:

// www/app/modules/core/services/chosen-hours-to-notification-data-transformer.service.js
'use strict';

angular.module('core')

    .service('ChosenHoursToNotificationDataTransformer', ['ApiLookup', 'DateProcessing', '$q', function(ApiLookup, DateProcessing, $q) {

        this.transform = function(chosenHours) {

            if (typeof chosenHours == 'undefined' || chosenHours.length == 0) {
                return [];
            }

            var _self = this;

            var deferred = $q.defer();

            deferred.resolve(ApiLookup.getAll().then(function(response){

                var apiData = response.data;

                chosenHours = _self.mergeApiDataWithChosenHours(apiData, chosenHours);

                if (chosenHours.length == 0) {
                    return []
                }

                var dateProcessedChosenHours = DateProcessing.process(chosenHours);

                _.each(dateProcessedChosenHours, function(element, index, list) {
                    dateProcessedChosenHours[index]['message'] = _self.messageHelper(dateProcessedChosenHours[index]);
                    dateProcessedChosenHours[index]['badges'] = element.length;
                });

                return dateProcessedChosenHours;
            }));

            return deferred.promise;
        };

There's quite a lot going on here - and this isn't the whole file.

The video contains a more in-depth break down of what's happening here, so instead, I am only going to focus on the promise.

First, we must create a new JavaScript object to contain our promise:

var deferred = $q.defer();

This is a very common operation and you will see it dotted throughout projects that use promises. There is cause for confusion in that there are multiple libraries that contain different implementations of promises. In this instance, we are using the Q library from Kris Kowal, because that's the promise library that Angular uses.

We then supply our implementation as to what we would like to happen in order for this promise to be resolved:

deferred.resolve(ApiLookup.getAll().then(function(response){

Skipping past that bit for the moment, what is initially more important is what we return:

return deferred.promise;

As in our McDonalds example, we aren't returning anything other than an object that promises it will contain a resolution at some point in the future.

Think of this as our customer's receipt.

This allows our application to continue, unblocked, whilst the outcome is determined at a later point in time.

This is important to realise, as you can't use the immediately returned result from this function in the way you might expect.

And Only Then

With promises, we have to wait until the outcome of an action has been determined, and then we can use the result.

To make things even more confusing, in this example:

deferred.resolve(ApiLookup.getAll().then(function(response){

    var apiData = response.data;

Our first promise will contain another function that returns a promise!

Let's break this down:

ApiLookup.getAll().then(function(response){

    var apiData = response.data;

Forget the deferred.resolve part.

ApiLookup is a service we have defined. Inside this service is function (.getAll()) which makes a call to somewhere to get back some data - our big list of TweetHours as it happens.

I say a call to somewhere, as what's nice about this is that the data could come from a remote API, or during development, it could come from a folder on our local disk containing a hardcoded JSON string. This is explained more in the video.

Now, this operation may take some time. We don't know how long it might take, so again, we return a promise that at some point, this data will become available.

Only when that data becomes can we move on to the next step:

  • WHEN we have the data
  • THEN do something
ApiLookup.getAll().then(function(response){

WHEN we have the data from our API, or our local JSON file, or whereever, THEN that data will be available to us in the function we define.

Our function parameter - response - could be called anything. I called it response as it's the expected response from our API call.

The rest of that function is straightforward JavaScript.

Making Sense Of Our Command

Now that we have ventured into the world of Promises, the second step in our command sequence should make a little more sense:

// www/app/modules/core/commands/prepare-hours-for-notification-service.command.js
'use strict';

angular.module('core');

    commangular.create('PrepareHoursForNotificationServiceCommand', [
        'ChosenHoursToNotificationDataTransformer',
        'SaveSelectedHoursToStorageCommand_result',
        function(ChosenHoursToNotificationDataTransformer, SaveSelectedHoursToStorageCommand_result) {
            return {
                execute: function() {
                    return ChosenHoursToNotificationDataTransformer
                        .transform(SaveSelectedHoursToStorageCommand_result)
                        .then(function(result){
                            return result;
                        })
                    ;
                }
            }
        }
    ], { resultKey : 'PrepareHoursForNotificationServiceCommand_result' })
;

If the outer section, or the resultKey part looks confusing, please watch the previous video and read the Commangular docs.

The interesting part here is:

return ChosenHoursToNotificationDataTransformer
    .transform(SaveSelectedHoursToStorageCommand_result)
    .then(function(result){
        return result;
    });

As we have seen, transform is going to take our data and merge it with the data returned from our API call.

The API call may take some time, so instead of blocking our program's execution until it has the result, it instead will delay the continued processing of the respective code sections until it has been given the data it needs to continue.

Only then, when that outcome has been resolved, will it return the data which becomes available to the next step:

    .then(function(result){
        return result;
    });

Here, we are simply saying, when we have the result, return it.

This is then passed on to the last command step in our sequence using the resultKey, as we have already discussed.

In the next video, we will see how all of this comes together to provide our Local Notification service with data in a format it can use.

Episodes