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.
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.
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
Aw, rejection… geolocation demo
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
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
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
Interacting with the external module… main calling code
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
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 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
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 function is executed the longitude and latitude will be injected into the HTML.
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
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
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.longitude properties along with a boolean
success property set to
After the custom event has been created the module then fires/dispatches the event. @ line 67 the
dispatchEvent method is invoked via the
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
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
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/
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
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:
- Scan your site for out-of-date libraries, layout issues, and accessibility
- Download free virtual machines for Mac, Linux, and Windows
- Check Web Platform status across browsers including the Microsoft Edge roadmap
- Remotely test for Microsoft Edge on your own device
More in-depth learning from our engineers and evangelists:
- Interoperability best practices (series):
- Coding Lab on GitHub: Cross-browser testing and best practices
- Woah, I can test Edge & IE on a Mac & Linux! (from Rey Bango)
- Unleash 3D rendering with WebGL (from David Catuhe)
- Hosted web apps and web platform innovations (from Kiril Seksenov)
Our community open source projects:
- manifoldJS (deploy cross-platform hosted web apps)
- babylonJS (3D graphics made easy)
More free tools and back-end web dev stuff:
Jump Start Git, 2nd Edition
Visual Studio Code: End-to-End Editing and Debugging Tools for Web Developers
Form Design Patterns