Home » Explaining ‘promises’ in D3.js: the what and the why

Explaining ‘promises’ in D3.js: the what and the why

  • by
promises-in-d3js
Want to do a random act of kindness? Share this post.

A while ago I started working in the JavaScript library D3.js to create some interactive visualizations. I even took a rather great Coursera course on the subject — Information Visualization: Programming with D3.js. If you’re not familiar with modern JavaScript syntax, D3.js has a rather steep learning curve. During this course and in some snippets I discovered, I bumped into a concept that was new to me: promises. In this blog post I explain what I learned about them.

First of all, you should know that promises aren’t something introduced by D3.js. On the contrary, it’s a concept that was introduced in JavaScript somewhere around 2015 and, consequently, is not compatible with old browsers such as Internet Explorer. Here’s a technical explanation. Promises areused to handle asynchronous operations in JavaScript. They are easy to manage when dealing with multiple asynchronous operations where callbacks can create callback hell leading to unmanageable code.” More on callback hell over here.

Here’s an example by geeksforgeeks.com that explains how it works. The code generates a promise that waits to be resolved or to be rejected. If it is resolved, the .then callback is invoked. If an error is generated, the .catch callback is invoked.

const myPromise = new Promise((resolve, reject) => { 
    if (Math.random()  > 0) { 
        console.log('resolving the promise ...'); 
        resolve('Hello, Positive :)'); 
    } 
    reject(new Error('No place for Negative here :(')); 
}); 
  
const Fulfilled = (fulfilledValue) => console.log(fulfilledValue); 
const Rejected = (error) => console.log(error); 
myPromise.then(Fulfilled, Rejected); 
  
myPromise.then((fulfilledValue) => { 
    console.log(fulfilledValue); 
}}).catch(err => console.log(err)); 

In the context of data visualization, promises are best interpreted as a container that will (or rather can) contain future values. Promises have become the default way to handle asynchronously loaded data as of D3.js v5. Nevertheless, the concept has been around for a while. For example, in his book, D3.js 4x. Data Visualization, Swizec Teller defines promises as follows: “a promise is an object that represents a value that may be available now, never, or sometime between those two extremes.

In the following code, I try to load in two CSV files: foo.csv and bar.csv using d3.csv. I treat these two operations as two promises. However, I pass them to Promise.all(). This returns a single promise which is resolved if all the individual promises are all resolved. It is rejected once a single individual promise is rejected. In other words, there will be no data at all if one or the other CSV cannot be loaded properly. Furthermore, both CSVs will wait for each other to be loaded, before continuing the code in the .then callback.

var bucket = {}

var promises = [d3.csv('foo.csv'), d3.csv('bar.csv')]
myDataPromises = Promise.all(promises)

myDataPromises.then(function(data) {
	data[0].map(function(d) {
		...
	});
	data[1].map(function(d) {
		...
	});
	bucket.first = data[0]
	bucket.second = data[1]
        generateViz()
})

myDataPromises.catch(function() {
	console.log('Something has gone wrong.')
})

This is really essential in data visualization. In many situations, you don’t want parts of your visualization generated if not all data has been loaded yet. Think about what will happen if you merge data and preprocess it before visualizing: it probably won’t work. In my example, I preprocess both data sources using the .map method, then I assign them to a bucket, where all the data resides. Finally, I trigger the function that actually generates the visualization.

I hope you’ll now understand how to work with promises.

Want to do a random act of kindness? Share this post.