The problem
Recently I had to fetch data from 2 sources and display them in a datatable.
The only requirement was to not show the data in the datatable until all the requests where finished. The current function, that worked for 1 source, was something like the one below (not the exact function 😅)
/*
dt is a variable that holds the DataTable object
*/
function fetchData(dt, url) {
$.ajax({
url,
dataType: 'json'
})
.done(function(res) {
res.forEach(a => {
dt.row.add([
a.id,
a.name,
a.username,
a.email,
a.phone
]).draw(false);
});
})
.fail(function(err) {
reject('error');
})
}
Why not call fetchData
twice, one for each data source?
That would fill the datatable with data from all the sources, BUT it would also violate my only requirement (do not show the data until all the requests are finished).
Since I had to wait I thought about two solutions:
Promises
One promise is created for each data source.
They are sent together and when all of them return (Promise.all
) then the data is added to the datatable.
function fetchData(url) {
return new Promise((resolve, reject) => {
$.ajax({
url,
dataType: 'json'
})
.done(function(res) {
resolve(res);
})
.fail(function(err) {
reject('error');
})
});
}
$(document).ready(function() {
var dt = $('#example').DataTable({
"ajax": function (d, callback, s) {
let promisesArr = [];
promisesArr.push(fetchData('/one-server-api/1'));
promisesArr.push(fetchData('/another-server-api/2'));
promisesArr.push(fetchData('users.json'));
Promise.all(promisesArr).then(values => {
// Convert [[1, 2], [3, 4], [5, 6]] to [1, 2, 3, 4, 5, 6]
let all = values.reduce((accumulator, currentValue) => [...accumulator,...currentValue]);
all.forEach(a => {
dt.row.add([
a.id,
a.name,
a.username,
a.email,
a.phone
]).draw(false);
});
});
}
});
});
Async / Await
The solution is quite similar.
function fetchData(url) {
return $.ajax({
url,
dataType: 'json'
})
.done(function(res) {
return res;
})
.fail(function(err) {
return 'error';
});
}
$(document).ready(function() {
var dt = $('#example').DataTable({
"ajax": async function (d, callback, s) {
let all = [];
all.push(...await fetchData('/one-server-api/1'));
all.push(...await fetchData('/another-server-api/2'));
all.push(...await fetchData('users.json'));
all.forEach(a => {
dt.row.add([
a.id,
a.name,
a.username,
a.email,
a.phone
]).draw(false);
});
}
});
});
Conclusion
Both promises
and async/await
solves the issue, but they do it, in a different way.
The key difference between the solutions is the time that they send the requests
The Promise solution sends all the requests at the (nearly) same time, the async solution waits for each request to finish before continuing to the next one.
So if you had 2 endpoints (sources):
- Endpoint 1 (E1): with an average response time of 1000ms
- Endpoint 2 (E2): with an average response time of 800ms
Promises
would:
- send the request to E1,
- send the request to E2,
- wait until both requests finish (avg 1000ms)
- add data
Total average time before adding data: 1000ms
Async/await
would:
- send the request to E1,
- wait until E1 request finishes (avg 1000ms)
- send the request to E2,
- wait until E2 request finishes (avg 800ms)
- add data
Total average time before adding data: 1800ms
Top comments (2)
and how to hide 'Loading' label then?
Ok, I solved this.
Replaced
with this one: