This article is part of a web development series from Microsoft. Thank you for supporting the partners who make SitePoint possible.

Transitioning from Callbacks and Messaging to Promises

A while ago, my son wanted to sell lemonade by the side of the road. You know how the story goes: we went to the store, got supplies and made the lemonade. Then he and his little sister sat by the side of the road. It was quite a first day, as they pulled in around $90 in lemonade sales!



The only downside… all day long he kept coming inside to make sure his accounting was correct. “Dad, did I charge enough?”, “Dad does this look like the correct amount?”, “Dad will you go to the store and buy me more lemonade and cups?” At the end of the day the three of us talked through building an application that would help them keep track of their finances. They thought it would be great if other friends could use the application and also keep track of where they put their lemonade stands up, along with the number of sales they made.

The Problem

Thinking through the application a few things came to mind. First, within the browser we will need to interact with the Geolocation API to find the latitude and longitude of the device. This browser interaction is asynchronous by nature. Second, this functionality to locate a person should be reusable so it could be utilized in other projects. Third, we need to specify an architecture (e.g. callbacks, messaging or promises) for the main JavaScript code to interact with the modularized geolocation module.

All the examples I’ll show today were written in Visual Studio Code which is a lightweight code-editor for Mac, Linux, or Windows.

Where am I: Geolocation

All modern browsers support the Geolocation API and fetching a device location. The Geolocation API is callback based, meaning as developers we need to pass functions into the API that will be called when the API is done processing. Geolocation is asynchronous, meaning that it could take 100 milliseconds or 100 seconds… there is no way to know how long it will take. What we do know is that the functions passed in as callbacks will be executed when the browser has finished processing the Geolocation request.

Beginnings of Geolocation… geolocation demo

Geolocation is accessed via the window.navigator object. The geolocation.getCurrentPosition method takes 3 parameters. For simplicity we will only use the first 2 parameters; a function to be called when the device location is found (i.e. resolveLocation ) and a function to be called when the device location is not accessible (i.e. rejectLocation ).

A successful interaction… geolocation demo

Notice @ line 10 the resolveLocation callback function is given a position object from the browser. Among other things this position object contains the needed latitude and longitude properties. They are accessed from the position.coords object.

Aw, rejection… geolocation demo

The rejectLocation callback @ line 27 is given an error object from the browser. This error object contains error codes and error messages. The error codes are numeric, ranging from 0 to 3. The 4 different error codes are defined in the ERROR_TYPE_CODES inferred constant array @ line 30. It should be noted that an error with a type code of 0 or 2 have extra information, hence the errorMessage string concatenation of error.code and error.message .

Reuse: Revealing Module Pattern

In a future article we will talk in-depth about modules. For this conversation we will simply use the Revealing Module Pattern. It will allow us to encapsulate the geolocation interaction within a module that could be used in any project, including whatever you are currently working on.

Modules for encapsulation & reuse… revealing module pattern demo

Let’s unpack this code inside out. The private/inner getLocationfunction @ line 12 will be the function that performs the geolocation lookup with browser. This private function is exposed via the Module API (i.e. the return block @ line 17). The return block is simply a JavaScript object for specifying the relationship between public methods and the private methods they hide.

Notice the inner getLocation and return block are wrapped in an Immediately Invoked Function Expression (i.e. IIFE). Most importantly an IIFE has a function that the browser invokes after it has been parsed. This invoking occurs via the () @ line 21. Secondly, a closure is formed due to the Module API being returned from the IIFE. This closure allows the state of the module (i.e. the function’s execution context) to live for the length of the entire application! This idea seems singleton-esque, as this module is only created once; however this is not the singleton pattern. Revealing modules are instantiated immediately after being parsed, whereas singletons are not instantiated until first use (e.g. their getInstance is invoked).

The guts of the revealing module pattern allows for the encapsulation of private variables that can only be accessed and changed via publicly defined getters and setters. In this location module the inner/private getLocation function is not directly accessible. Only via a call to the public DI.location.getLocation method will provide the ability to call the inner/private getLocation function scoped to the anonymous function.

Lastly, we create a namespace @ line 7. Within DevelopIntelligence we use DI as our namespace object. This allows all DevelopIntelligence code to be stored on only one object attached to the global JavaScript window object, minimizing the pollution of the global object.

Interacting with the external module… main calling code

The main.js JavaScript file calls the location module’s public getLocation method via the DI namespace which in turn executes the private getLocation method functionality.

Modularize the Geolocation

So far we have seen how to utilize geolocation within the browser. We have also seen how to write a module, via the Revealing Module Pattern. Let’s put those together so we have an actual modularized Geolocation interaction.

Modularized Geolocation… location module demo

In the code above the call to the Geolocation API has been inserted into the private/inner getLocation function @ line 9. Building a module in this fashion abstracts away the implementation details of dealing with Geolocation. It also allows the location-module.js file to be used in any project without ever having to duplicate those implementation details of the resolveLocation and rejectLocation function.

Communication: Speak Module, Speak!

We have one glaring problem with this setup! We haven’t dealt with the asynchronous nature of geolocation in respect to module interaction. When the main.js file calls the geolocation module we have not defined a way to pass back the longitude and latitude properties out of the module and back into the main calling code. At this point we are only able to tell the location module to do work, but have no way of knowing when it completes to interact with the data. Let’s look at a few different ways to handle this architecturally: first callbacks, then messaging and finally promises.

Callbacks

Callbacks provide a guaranteed call and response between the main calling code and the geolocation module. However, that guaranteed interaction comes at the price of being tightly-coupled.

We have seen a callback architecture in the wild when we utilized the Geolocation API. Remember, when we called the window.navigator.geolocation.getCurrentPosition function we had to pass in functions that would be called when the API was done processing its asynchronous code. This was tightly-coupled because the calling code needed to know the implementation details of how to handle the position object that was handed back to the successful callback and the implementation details of how to handle the error codes and messages handed back to the failure callback.

A callback based approach to Geolocation… callback calling code example

In the code above @ line 18 our main JavaScript functionality passes a callback function reference to the module. The module will execute this callback when the module’s asynchronous code has completed. The beauty of a callback based architecture comes in the guarantee that the passed in callback function reference will be executed when the module deems necessary.

A callback based approach to Geolocation continued… callback module code example

The location module no longer simply prints the position coordinates via the console. Rather @ line 44 the module executes the successCallback callback function reference that was passed into the getLocation function signature. Remember the function that is actually going to be called is the injectCoordinates located in the index.js JavaScript file. When the injectCoordinates function is executed the longitude and latitude will be injected into the HTML.

Callbacks give a guaranteed communication architecture between the main JavaScript and the called module. However, this architecture comes at a cost in terms of readability, understandability and coupling as the main JavaScript hands application control over to the module to run a callback at a future point in time.

Messaging

Messaging provides us a loosely-coupled architecture allowing all of the implementation details to be hidden within the module. However, this loosely-coupled architecture comes with the price of having no guarantee of response from the module to the main calling code.

Within a messaging architecture the main calling code only needs to know how to ask for data from the module and how to listen for a response. After the asynchronous code is run within the module an event is fired, containing the data, allowing all listening code a chance at interacting with the data.

A messaging based approach to Geolocation… messaging calling code example

Custom JavaScript events are utilized to create a messaging architecture. To facilitate a looser coupling the location module will expose the type of Custom Event the module has created. @ line 23 the main calling code stores the custom event type and then @ line 26 it registers an event listener for that specific event type.

When the location module finishes its asynchronous code it will fire the custom event and the registered event listener’s callback function will be called. On a successful interaction @ line 28-29 the injectCoordinatesfunction will be called and insert the longitude and latitude into the HTML.

A messaging based approach to Geolocation… messaging module code example

As mentioned above, the messaging based location module will expose a Custom Event Type via the getEventType function along with the location via the getLocation function. This allows the location module to change event types without having to change any code in the main JavaScript file. In other words the code calling this module doesn’t need to know it is a custom event of type location .

A messaging based approach to Geolocation continued… messaging module code example

The location module will create a new custom event that passes the data retrieved from the geolocation interaction. It utilizes the CustomEvent constructor with an event type (i.e. location ) and a messageobject set to the detailproperty @ line 66. resolveLocation the successful callback creates a message object containing the coordinates.latitude and coordinates.longitude properties along with a boolean success property set to true .

After the custom event has been created the module then fires/dispatches the event. @ line 67 the dispatchEvent method is invoked via the document.body element to fire the created custom event. Once the event is fired the browser runs the event handler callback function previously registered within the main JavaScript file, which in turn inserts the latitude and longitude into the HTML.

Promises

Promises provide us the benefits of the callback and messaging architecture. They give a guaranteed delivery of data, while not forcing the knowledge of implementation details. After the modules asynchronous code is run the data is handed back to the calling code via a predefined API.

A promise based module differs from callback based module in that it never controls the application. The calling code asks the promise module to do something and when it’s done it hands its collected data back to the calling code. The promise module isn’t in charge of invoking a method back on the calling code. This means it never needs to hold onto a reference for a specific function defined within the main calling code, allowing for a more loosely-coupled architecture than a callback based architecture.

A promise based module differs from messaging based modules in that the promise architecture guarantees a response to the calling object. The calling code asks the promise module to do something and immediately a transactional receipt (i.e. a promise object) is handed back to the calling code. On the transactional receipt the calling code sets up a callback function that will be run when the promise module is done. This means there is no guessing or hoping that the calling code has registered the correct event listener type to the correct DOM element, guaranteeing that the calling code will get the data needed when it is ready. Also, the promise architecture gives a pre-defined, consistent API taking away the guesswork of handling the return of asynchronous data from a modules data.

A promise based approach to Geolocation… promise calling code example

There are many different frameworks that utilize a promise based architecture. jQuery utilizes a Deferred Object, Kris Kowal created a q library, Angular utilizes a form of that library in the $q service, and now ES6/ES2015 has promises baked in.

Above we are interacting with ES6/ES2015 promises. To see the others in action hit up our DevelopIntelligence GitHub Repo on Promises. Notice @ line 18 in the calling code a call is made to the DI.currentLocation module. As I previously stated, immediately a transactional receipt is handed back, (i.e. a promise object). This promise object exposes standard API methods for the calling code to interact with.

The most often used method in the promise API is the .then method. It is supported across all promise framework implementations. Within the .then method we specify functions that will be run when the DI.currentLocation module is done processing its asynchronous code. In this case when the browser resolves a latitude and a longitude, the DI.currentLocation module hands that data back as an object to the calling code via a parameter in the .then method. As a side-note, interacting with that data object returned within the .then method can be referred to as “unwrapping the promise.”

A promise based approach to Geolocation… promise module code example

It’s time we saw how to create promises instead of callbacks or messaging. With the new ES6 / ES2015 Promise Object baked in, we create a promise object instance from the Promise constructor via the new operator as can be seen @ line 31. Within the Promise constructor we define an anonymous function receiving a resolve and reject callback method.

Think back to the callback architecture example above. Within the promise module (i.e. the geolocation module) there were similar callback methods which were defined as successCallback and failureCallback . The resolve and reject callbacks are to be called when the asynchronous data is available for interaction. In other words with the browser’s gets its current position, the resolve method is called and the coordinate data will be sent out of the module. At that point the .then method that was defined within the calling script will be triggered and the location data will be processed and placed on the browser.

See promises in action @ http://promise-blog.azurewebsites.net/promise-es6/

The Takeaway

Interacting with asynchronous data introduces complexity into an application. When interacting with a device’s geolocation, we as developers, are at the mercy of when that data will be given to our application for processing. Promises give us a convenient, pre-defined API to manage the asynchronous complexity. They provide a way for developers to have the loose-coupling of a messaging architecture and the guarantee of a callback architecture.

More Hands-on with Web Development

This article is part of the web development series from Microsoft and DevelopIntelligence on practical JavaScript learning, open source projects, and interoperability best practices including Microsoft Edge browser and the new EdgeHTML rendering engine. DevelopIntelligence offers instructor-led JavaScript Training, AngularJS Training and other Web Development Training for technical teams and organizations.

We encourage you to test across browsers and devices including Microsoft Edge – the default browser for Windows 10 – with free tools on dev.microsoftedge.com:

More in-depth learning from our engineers and evangelists:

Our community open source projects:

vorlon.JS (cross-device remote JavaScript testing)

manifoldJS (deploy cross-platform hosted web apps)

babylonJS (3D graphics made easy)

More free tools and back-end web dev stuff: