ArcGIS JavaScript Promises and Web Mobile (Part 1)

3813
4
08-05-2015 03:24 PM
AndyGup
Esri Regular Contributor
6 4 3,813

If you haven't had a chance to read up on what's coming in the next major release of the ArcGIS API for JavaScript v4, then it's time to get your game on and brush up on some of the exciting changes that can be explored today in v4 beta1. For those of you who haven't heard of this API or ArcGIS, we provide a large selection of APIs, SDKs, services, content and applications for building commercial mapping/geo-spatial applications. You'll want to check out the following:

  • The 4.0 API is currently in beta, so give v4beta1 a spin. This version of the API is a rewrite and it is fundamentally based on using JavaScript Promises. With these promise-based coding patterns you will be adopting a new and powerful architectural change in how you do JS app development.
  • Get a quick overview of Working with Promises (and the ArcGIS API for JavaScript).
  • Read about promises as guarantees in ArcGIS JavaScript Promises.
  • And, here's a great intro article from HTML5 Rocks simply called JavaScript Promises.

I promise to...?

Promises bring a whole new level of application life-cycle control to building web applications for use on mobile devices. In particular, there are two aspects of promises that will change how you do mobile development, forever: the ability to look back in time to see if some process has completed, and the ability to easily manage multiple, asynchronous requests.

In part 1 of this series we'll chat about how promises will allow you to verify after-the-fact and in a stateful way if an asynchronous action has completed successfully or failed. This is such an important concept that I'll illustrate it in a variety of ways to help push home the basics. All the examples are written for the ArcGIS API for JavaScript v4.x.

Absolutely yes these ideas work just fine on desktop apps. But desktop apps, in general, are significantly less susceptible to life-cycle issues as compared to mobile apps. Desktop web apps are tapped into dedicated internet connections with fairly reliable up times and connection speeds. In comparison, mobile web apps oftentimes easily suffer from small connectivity interruptions, hiccups and slowdowns. This can play havoc on your app while it's in the process of downloading and parsing HTML, CSS, JS and images as well as going through the initialization process. Promises can go a long way to smoothing out these potential speed bumps and towards helping you build consistent and stable applications without bending, breaking or stretching the basic rules of JavaScript best practices and sensibilities.

Promises, promises: the .then() pattern

All this magic works because promises have a built-in .then() mechanism that is your ticket to getting access to an asynchronous processes' state at any time whether it is still pending, if its completed or even if it failed. Simple in concept, powerful in execution. So, let's dig in.

We mention this several times in the ArcGIS API for JavaScript v4 documentation, the basic structure and concept of a promise looks like this:

someAsyncFunction.then(callback, errback);

A related side note, until the ECMAScript 2015 Promise standard is fully adopted and integrated by the various browser vendors, some third party libraries may provide slightly different syntax than what is shown in this post, but the concepts should all generally lead to the same result.

Below is a slightly exaggerated code snippet of how the concept works, and for a fully working version check out this jsbin. In this sample, a timer forces the code to wait for 5 seconds before calling view.then(). If you run the jsbin with the developer console window open you'll see the map has already loaded and should be visible by the time "View is ready? true" gets written to the console. The point here is that the SceneView's promise did, in fact, let us query its state well after the map and view were initialized.

     map = new Map({
          basemap: "streets"
     });

     view = new SceneView({
          container: "viewDiv",
          map: map,
          scale: 240000000
     });
            
      // Let's wait for 5 seconds
      setTimeout(function() {
           view.then(function(viewEvt){
                console.log("View is ready? " + viewEvt.ready);
           });
      }, 5000);        

Huh, how does this benefit me? I still don't get it.

Hang in there, we have a few more examples to look at. The above example demonstrates that the promises pattern truly lets you de-couple the timing on when you execute various aspects of your code. It means you can access the state of an asynchronous process at any time and anywhere in your application, even after the process has long been completed. It gives you another excellent tool in your coding toolkit for going several steps beyond what's possible with using only event listeners and callbacks.

The most important benefit of this pattern for mobile development, or really for any JavaScript development, is you can delay asking for a promise if you need to wait for other asynchronous or synchronous processes to complete first. You can't delay an event listener and it's bad, bad practice to place timers in callbacks. Promises give you a scalpel for exerting significantly greater control over application logic flow than was ever possible before with simply using event listeners and stand-alone callbacks by themselves.

Okay, so how's this different than events and event listeners?

The promise-based .then() pattern offers a significant advantage over the tried-and-true, events-based coding pattern. JavaScript event messages are similar to bullets; once they are fired they are gone forever and you can't bring them back. Once an event has fired you can't get it back, and it only hangs around for a very short period of time as it bubbles its way up through various layers of JavaScript and then it's gone. Poof.

What this means is if you initialize an event listener in your app after an event has already occurred then the event listener will never fire. Period. It will simply wait faithfully for eternity and pretty much do nothing.

Here's a snippet to demonstrate the concept of an event listener created after-the-fact and will never fire. You can also check it out live in a jsbin version.


            map = new Map({
                basemap: "streets"
            });

            view = new SceneView({
                container: "viewDiv",
                map: map,
                scale: 240000000
            });            


            view.then(function(viewEvt){

                console.log("View is ready? " + viewEvt.ready);

                //Event listener is initialized after the event has occurred!
                map.on("load", function(){
                    // This will never fire!
                    console.log("Map loaded via event listener!");
                });

                console.log("The map already loaded? " + map.loaded);

            });           

Yes, this snippet is an oversimplification to help clarify how event listeners can easily cause life-cycle issues, and I'm definitely not implying that all event listeners are bad. The point is that it's very easy in large JavaScript applications to initialize an event listener too late in the life-cycle for it to ever fire properly. What's happens next is you troubleshoot and wonder for hours why your code isn't working properly, or why it may only work intermittently.

In mobile web applications, this simple concept is perhaps one of the most common reasons for intermittent failures!

What happens if you swap an event listener for a promise?

This next sample demonstrates swapping out the failed event listener pattern from the previous example with a promise-based pattern. Here's the jsbin for this sample. As you migrate existing applications, you'll be going through similar steps.

There is also an important twist in this sample that steps away from simply checking if the map.loaded boolean is true or false and then continuing with code execution. With the promises-based pattern we can inspect the promises' callback object and verify, among other things, if the process completed successful or if it failed and why.

We always have access to the promise associated with map, and we can access it at any time and anywhere in our application.


            map = new Map({
                basemap: "streets"
            });

            view = new SceneView({
                container: "viewDiv",
                map: map,
                scale: 240000000
            });

            view.then(function(viewEvt){
                console.log("View is ready? " + viewEvt.ready);

                   map.then(function(mapEvt) {
                        console.log("The map already loaded? " + mapEvt.loaded);
                    }, function(mapErr){
                        console.log("Was there any map load errors? " + mapErr);
                    });

                console.log("The map already loaded? " + map.loaded);
            });           

So, let's take this one step further and ask for the map.then() promise after a 5 second timer run. This is similar to what we did with the event listener above, and the purpose is to simulate asking for the promise at some point signfiicantly later in the application life-cycle than the process occurred. The big difference is this pattern will, in fact, be successful because the promise maintains state! Try modifying the above sample in jsbin and try it out yourself.


            map = new Map({
                basemap: "streets"
            });

            view = new SceneView({
                container: "viewDiv",
                map: map,
                scale: 240000000
            });

            view.then(function(viewEvt){
                console.log("View is ready? " + viewEvt.ready);

                setTimeout(function() {
                    map.then(function(mapEvt) {
                        console.log("The map already loaded? " + mapEvt.loaded);
                    }, function(mapErr){
                        console.log("Was there any map load errors? " + mapErr);
                    });
                }, 5000);

                console.log("The map already loaded? " + map.loaded);
            });           

Closing thoughts

There is a learning curve associated with promises, and they aren't perfect. However, hopefully this post has helped you understand just a little bit more about the value that promises can provide. As you consider the possibilities, you might start to rethink how you architect applications and that is a good start to continuously improve what we create!

Promises, callbacks and event listeners all still have important roles play depending on the unique requirements for the applications we build on a daily basis. In fact, many people overlook or ignore the fact that a promise does indeed include its very own callbacks to indicate if the process completed successfully or if it failed.

As you start to migrate existing applications to the promises pattern you may find yourself mixing-and-matching promises, callbacks and event listeners as you go through the process of reinventing your architecture. This is all part of the normal progression as new architectures gradually take over the old. And, as you build new applications from scratch you'll get to try your hand at a fresh approach.

Try these samples out, check out the ArcGIS API for JavaScript v4 samples and hopefully have fun learning the latest and greatest that is available in JavaScript!

[Minor edit - Aug 10, 2015 to read as 'any' async request!]

4 Comments
About the Author
Sr. Product Engineer - ArcGIS API for JavaScript.