D3.js v5: Promise syntax & examples

Here is my take on promises, the latest addition to D3.js syntax. The other week I attempted to brush up on my D3.js skills and got stuck at the most basic task of printing csv data on my html webpage. This is how I learnt that version 5 of D3.js substituted asynchronous callbacks with promises – and irreversibly changed the way we used to work with data sets. Getting your head around promises can take time, especially if you – like me – aren’t a JavaScript programming pro. In this post I’ll share my lessons learnt and provide some guidance for the ones lost in the world of promises.

Imagine I wanted to visualise some circles based on data stored in a csv file. My file is called circles.csv and it looks like this:

x,y
10,2
11,4
2,8
2,9
3,10
10,5
8,8
6,7

Traditionally, to load it to D3, I would do the following:

const mycircles = d3.csv("circles.csv", function(data) {
    console.log(data);
    });

Then I would use the mycircles variable by calling it from my function as .data(mycircles). This process is very different in the latest version of D3. Now instead of loading the dataset we create a promise that can be referenced by the functions later on. Thanks to this, our application doesn’t have to wait for a dataset to be generated before it’s actually used. The promise way of doing things is something like this:

promise_1().then(function () {
    return promise_2();
}).then(function () {
    return promise_3();
}).catch(console.log.bind(console));

So, if we wanted to visualise our circles, we’d do:

const mycircles = d3.csv("circles.csv");
mycircles.then(function(data){
     return
          svg.selectAll("circle")
                .data(data)
                .enter()
                .append("circle")
                .attr("cx", function(d, i) {
                    return (i * 50) +25; })
                .attr("cy", h/2)
                .attr("r", function(d) {
                    return d.x; });
                });

If we didn’t act on a promise, but instead called a promise object from within the function (as we’d do the old way), our code would return undefined.

Another important change is around for each loops. Promise syntax uses its own – a more optimised – way to loop through elements of an array, Promise.all. Below is an example case where I had to create an array of longitude and lattitude coordinates returned from a csv.

// first promise returns the dataset
const dataset = d3.csv("coords.csv").then(function(data)
    {return data;
    });
//this promise returns an array with lattitude and longitude
const coords = dataset.then(function(value) {
   return Promise.all(value.map(function(results){
   return [results.Lattitude, results.Longitude];
    }))});
//print the array
coords.then(function(data) {
    return d3.select("body").append("p").text(data);});

It took me a while to work this stuff out – hopefully it will be useful to others. Let me know in the comments if you know of other examples that use the new syntax – or if you spot any issues with my solution. In my research I found two sources that were especially helpful: