Introducing asynchronous code and basic Promises
In KeyLines 3.3, we announced support for JavaScript Promises. We know some of you have been coding with Promises for some time, so we wanted to give you the option to use them in KeyLines too.
Understanding Asynchronous Programming
To understand promises, you must first understand the concept of asynchronous programming.
Regular synchronous code executes functions one after another, waiting for function 1 to finish before executing function 2, and so on. In this example, if function 2 takes time to complete, e.g. an AJAX call or DOM change, it delays the execution of function 3, and every function further down the chain:
![Synchronous code executes functions one after another, waiting for one to complete before starting the next](/wp-content/uploads/2017/01/javascript-promises-1-synchronous-code.png)
Take KeyLines chart.layout function as an example. With a synchronous implementation, a computationally-heavy chart layout across thousands of nodes could create significant delays, block further functions, and frustrate users.
JavaScript, on the other hand, can be asynchronous (async). Functions are still executed in order, but the program doesn’t wait for the previous function to complete before beginning the next:
![Unless you tell it otherwise, async code will not wait for one function to complete before starting the next](/wp-content/uploads/2017/01/javascript-promises-1-asynchronous-code.png)
Instead, time-consuming functions like chart.layout are initiated with a ‘callback’ parameter (more on that later), and KeyLines moves on to the next function. It’s faster and more responsive for users.
![The execution of four functions in synchronous and asynchronous code](/wp-content/uploads/2017/01/javascript-promises-3-comparison.png)
Managing asynchronous workflows
In scenarios where we don’t want multiple functions to overlap, we can use callbacks. Callbacks are function references passed as a parameter to an async function. These callback functions will themselves be called by the async function once it’s completed its operations.
Say, for example, you want to load new data into a chart and instantly run a layout. Both chart.load and chart.layout are async and require time.
Without correct callback management, we may run a layout before the data has finished loading. To force the browser to run this code sequentially, we pass the layout callback in the load function as an argument, like this:
function doLayout () { chart.layout("standard"); } chart.load(data, doLayout);
Now the layout won’t be performed until after the load function is complete.
This extra power and flexibility is great for developers, but in real-world applications with complex logic, things can soon get complicated with endless nested callbacks. To avoid this ‘Callback Hell’ – we use Promises…
What is a JavaScript Promise?
Promises are an alternative way to manage async workflows. They’ve been around for some time, but were first natively supported in the ES6 iteration of JavaScript.
Instead of passing a function as an argument to another function (the callback approach), we tell the code to create placeholders for the as-yet-unknown results.
An async function will return a new Promise object. The Promise object is then resolved if the function is successful, or rejected if it is unsuccessful. As we’ll see, they also help you write cleaner, less error-prone code.
How to use Promises in your KeyLines code
Implementing Promises in your KeyLines code is easy. First, use KeyLines.promisify() to make the API Promise-aware:
// Enable Promises KeyLines.promisify();
And you’re ready to start. For example:
chart.load( data ) .then( function(){ return chart.layout(); });
KeyLines also supports third party implementations of Promises, like the Angular $q object:
// Enable Promises KeyLines.promisify( Q.Promise ); // Use Promises chart.load( data ) .then( function(){ return chart.layout(); });
Actions in sequence
The advantages of using Promises are clear when we try to organize code into a sequence of functions. Let’s work through the following actions:
- Load some data
- Zoom to fit
- Run a layout
- Apply a ‘ping’ effect on a specific node
These need to run in order – we can’t ‘ping’ a node before it’s loaded. We can sequence them by chaining Promises:
chart.load(data) .then(() => chart.zoom('fit')) .then(() => chart.layout('standard')) .then(() => chart.ping('d5'));
Actions in parallel
In this example, we’ll use promises to change an item’s color and run a layout simultaneously.
Use Promise.all() to delay execution until both functions are resolved:
// a new function to animate a color change of an item function changeColor( newColor ){ return function(){ return chart.animateProperties( {id: ‘itemId’, c: newColor }, time: 800 ); }; } // run a layout and change color simultaneously var actions = [ layout(), changeColor( ‘red’ ) ]; Promise.all( actions ).then( function(){ // execute this code when both actions have completed ... });
Handling Errors with Promises
And finally, a quick word on handling errors. It’s not always easy to check for errors in async code using callbacks. Using Promises simplifies this. We can use the Promise object’s ‘resolved’ or ‘rejected’ information, via a .catch method, to handle errors:
chart.load( data) .then( function(){ return chart.layout(‘wrong’); }) .catch( function(){ // handle a possible error here }) .then( function(){ // now carry on! return chart.layout(‘standard’); });
Try it yourself
Want to try KeyLines’ Promises for yourself? Request a trial to get started.