Reputation: 3142
I have reached a stage where I believe I am not understanding the correct callback of events. I am utilizing d3.js v5 and my issue is as follows.
I have a function which parses a tsv file and adds data to a global array selectedData
using the d3.tsv
function which utilizes promises within a function addData
:
//function takes filename and data description to add to the selectedData global variable
function addData(filename, dataDesc) {
d3.tsv(filename, type).then ((data) => {
data.forEach(e => {
var obj = {
Title: dataDesc,
ID: e.ID,
Value: e.Value,
}
selectedData.push(obj);
});
});
}
function type(d) {
d.Value = +d.Value;
}
Now I use this function to either add a single data file or can call it multiple times to add data from multiple data files into this global variable. The global variable is utilized for a number of other functions but as a start it is used to produce a bar chart.
For adding a single file, I would like to produce a bar chart after adding the data to the global variable. To do so I use the following function:
//functions adds one data file
function addOneData() {
filename = userfile; //gets the user file name based on <input> element using the FileReader api
dataDesc = document.getElementById('file_title').value; //gets the Descriptive Title of the data
addData(filename, dataDesc); //add data to the global variable of selectedData
drawBarChart(); //draws a grouped bar chart based on the global array of selectedData
}
I have a similar function which loops through an array of multiple data files as well to add them to the global variable with different filenames
and dataDesc
. For the multiple files I do not want the drawing to happen after each file, which is why the drawBarChart
function is not a part of the addData
function.
Now my issue is that the functions of addData
and drawBarChart
are running asynchronously and therefore drawBarChart
runs much before the data is added to the selectedData
global variable. So my question is, how can I make this either synchronous or use the power of promises? I have been trying promises and async/await but it feels like I am missing out on some key concepts. Any help is appreciated. Thank you.
Upvotes: 1
Views: 1236
Reputation: 102174
"Create synchronous loading of data" is something that doesn't make much sense in this case... Instead of that, accept the nature of asynchronous code.
Also, you said:
For the multiple files I do not want the drawing to happen after each file, which is why the drawBarChart function is not a part of the addData function.
But that's what your addOneData
function is doing right now: it's calling drawBarChart
for each file (wrongly, as you know, due to the asynchronous nature of addData
).
So, let's fix your two problems, the asynchronous code and calling drawBarChart
after loading multiple files.
An easy solution is passing drawBarchart
as a callback in another then
method, like this:
function addData(filename, dataDesc, callback) {
d3.json(filename).then((data) => {
data.forEach(e => {
var obj = {
Title: dataDesc,
Value: e
}
selectedData.push(obj);
});
}).then(callback)
};
Here is a demo with a very simple JSON I created online (which requires d3.json
, but the general principle is the same of your d3.tsv
and it works just as a TSV for your case):
var selectedData = [];
addOneData();
function addData(filename, dataDesc, callback) {
d3.json(filename).then((data) => {
data.forEach(e => {
var obj = {
Title: dataDesc,
Value: e
}
selectedData.push(obj);
});
}).then(callback)
};
function addOneData() {
filename = "https://api.myjson.com/bins/m1vsl";
dataDesc = "FooBar"
addData(filename, dataDesc, drawBarChart);
};
function drawBarChart() {
console.log(selectedData)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
As you mentioned you may have many files, but you want to call drawBarChart
only once. In that case, use Promise.all
and an array with your filenames:
function addData(filenames, dataDesc, callback) {
Promise.all(filenames.map(d => d3.json(d))).then((alldata) => {
alldata.forEach(data => {
data.forEach(e => {
var obj = {
Title: dataDesc,
Value: e
}
selectedData.push(obj);
});
});
}).then(callback)
};
Here is the demo:
var selectedData = [];
addAllData();
function addData(filenames, dataDesc, callback) {
Promise.all(filenames.map(d => d3.json(d))).then((alldata) => {
alldata.forEach(data => {
data.forEach(e => {
var obj = {
Title: dataDesc,
Value: e
}
selectedData.push(obj);
});
});
}).then(callback)
};
function addAllData() {
filenames = ["https://api.myjson.com/bins/m1vsl", "https://api.myjson.com/bins/rncvp"];
dataDesc = "FooBar";
addData(filenames, dataDesc, drawBarChart);
};
function drawBarChart() {
console.log(selectedData)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
Upvotes: 5