Tuesday, December 3, 2024

Asynchronous Programming in ECMAScript 6

Asynchronous Programming in ECMAScript 6

TC39 is the organization in charge of standardizing JavaScript (or ECMAScript to be exact). They meet every two weeks, usually in the California Bay area, to discuss the ECMAScript specification and where it’s heading. The 6th edition, officially known as ECMAScript 2015, was finalized in June 2015, and is widely gaining support across major browser vendors with almost 90% coverage at the time of writing. Among a lot of great improvements, ECMAScript 6 standardizes the usage of promises (formerly known as futures). Promises have been around for a while but now we don’t need to use a third-party library anymore as they are provided natively by the browser. In today’s article, we’ll learn how to use promises to have our functions return their results asynchronously.

Some Background

As a language created for the Web, JavaScript needed to be able to respond to asynchronous user interactions such as clicks and key presses from the beginning. Node.js further popularized asynchronous programming in JavaScript by using callbacks as an alternative to events. As more and more programs started using asynchronous programming, events and callbacks were no longer powerful enough to support everything developers wanted to do. Promises are the solution to this problem.

So what is a promise? It’s an object that provides deferred and asynchronous operations. A promise represents an operation that hasn’t completed yet, but is expected to in the future. As we’ll see in a moment, promises are a way of organizing asynchronous operations in such a way that they appear synchronous.

Promise Basics

To implement such a function, you return a promise, an object that is a placeholder for the result. The caller of the function registers callbacks with the promise to be notified once the result has been computed. The function sends the result via one of the two promise arguments.

var promise = getUserData('https://randomuser.me/api/?nat=us');

As you can see below, the Promise constructor accepts a function with two arguments, which are also functions. The first argument – typically called “resolve” – is to be invoked when your function has completed successfully. The second – typically called “reject” – is invoked if your function fails or encounters an error of some sort.

function getUserData(url) {
  return new Promise(
    function (resolve, reject) {
      $.getJSON( url, function( data ) {
        // We can resolve the promise
        resolve(data);
      }).fail(function() {
        reject(new Error("Unable to load data: " + this.statusText));
      });
    });
}

The then() Function

The then() function is available to all promise instances and also accepts two arguments. The first argument is a function called by resolve() when the promise is fulfilled. Any additional data related to resolve() is passed along to this fulfillment function. The second argument is a function called by reject() when the promise is rejected. Similar to the fulfillment function, the rejection function also receives any additional data related to the rejection.

promise.then(
   function (value) { /* fulfillment */
     var person = value.results[0];
     console.log(person.name.last + ', ' + person.name.first);
   },
   function (reason) { /* rejection */
     console.log(reason);
 });

Both arguments are optional, so you can omit one if you wish. (You probably don’t want to omit both!) For example, this optimistic call to then() ignores errors.

promise.then(
   function (value) { /* fulfillment */
     var person = value.results[0];
     console.log(person.name.last + ', ' + person.name.first);
   });

The catch() Function

An alternate form of the above code is to chain the then() call to catch(). It handles error conditions just like the second argument above.

promise.then(
   function (value) { /* fulfillment */
     var person = value.results[0];
     console.log(person.name.last + ', ' + person.name.first);
 }).catch(
   function (reason) { /* rejection */
     console.log(reason);
 });

Using Anonymous Functions

Just as anonymous functions are often passed to event handlers, they may also be passed directly to the Promise constructor. Using this style, there really isn’t any need to store the Promise to a variable, unless you want to refer to it later.

new Promise(
  function (resolve, reject) {
    $.getJSON( 'https://randomuser.me/api/?nat=us', function( data ) {
      // We can resolve the promise
      resolve(data);
    }).fail(function() {
      reject(new Error("Unable to load data: " + this.statusText));
    });
  }).then(
    function (value) { /* fulfillment */
      var person = value.results[0];
      console.log(person.name.last + ', ' + person.name.first);
  }).catch(
    function (reason) { /* rejection */
      console.log(reason);
  });

I have included demos of both the named function and anonymous function styles above. It calls the free, open-source RANDOM USER GENERATOR API for generating random user data and displays the user’s name in a “Last name, First name” format.

Conclusion

There’s a lot more to Promises than that, but at least we’ve got the basics covered. In the next instalment, we’ll look at Unsettled Promises, the Promise.resolve() and Promise.reject() static methods, and some other interesting tid bits.

Rob Gravelle
Rob Gravelle
Rob Gravelle resides in Ottawa, Canada, and has been an IT guru for over 20 years. In that time, Rob has built systems for intelligence-related organizations such as Canada Border Services and various commercial businesses. In his spare time, Rob has become an accomplished music artist with several CDs and digital releases to his credit.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Popular Articles

Featured