When the function below finishes and provides a finalized list of items in the array 'albums', I want it to call another function/do something else with the list.
Currently it posts [] before the function finishes and I know that's because of asynchronous execution, but I thought Node read linearly since it's single threaded?
function getAlbumsTotal(list, params){
for(var i = 0; i<list.length; i++){
api.getArtistAlbums(list[i], params).then(function(data) {
for(var alb = 0; alb<data.body.items.length; alb++){
albums.push(data.body.items[alb].id);
}
}, function(err) {
console.error(err);
});
}
console.log(albums);
//do something with the finalized list of albums here
}
nextTick() is used to schedule a callback function to be invoked in the next iteration of the Event Loop. setImmediate() method is used to execute a function right after the current event loop finishes. 2.
The event loop is what allows Node. js to perform non-blocking I/O operations — despite the fact that JavaScript is single-threaded — by offloading operations to the system kernel whenever possible. Since most modern kernels are multi-threaded, they can handle multiple operations executing in the background.
Event loop is an endless loop, which waits for tasks, executes them and then sleeps until it receives more tasks. The event loop executes tasks from the event queue only when the call stack is empty i.e. there is no ongoing task. The event loop allows us to use callbacks and promises.
To handle the asynchronous nature of JavaScript executions, you can use one of the three available methods to wait for a function to finish: This tutorial will help you learn all three methods, starting from using callback functions A callback function is a regular JavaScript function that you pass to another function.
This simulated wait is accomplished by keeping a counter that only increases when the async function finishes. Doesn’t matter if it errors out or succeeds. When this counter matches the array length we know that we’re finally done with the transformations and we can quit.
This simulated wait is accomplished by keeping a counter that only increases when the async function finishes. Doesn’t matter if it errors out or succeeds. When this counter matches the array length we know that we’re finally done with the transformations and we can quit. By quit, I mean return the transformed array back to the user.
Like mentioned earlier we can’t just call res.send after the loop ends because the transformations may not have finished. Instead in the above code we’re waiting until the last asynchronous function call finishes. This simulated wait is accomplished by keeping a counter that only increases when the async function finishes.
The callback function you provide to then
is indeed executed asynchronously, which means it is only executed after the rest of the code in the current call stack has finished executing, including your final console.log
.
Here is how you could do it:
function getAlbumsTotal(list, params){
var promises = list.map(function (item) { // return array of promises
// return the promise:
return api.getArtistAlbums(item, params)
.then(function(data) {
for(var alb = 0; alb<data.body.items.length; alb++){
albums.push(data.body.items[alb].id);
}
}, function(err) {
console.error(err);
});
});
Promise.all(promises).then(function () {
console.log(albums);
//do something with the finalized list of albums here
});
}
NB: Apparently albums
is defined as a global variable. This is not so good a design. It would be better that each promise would provide its own subset of albums, and the Promise.all
call would be used to concatenate those results into a local variable. Here is how that would look like:
function getAlbumsTotal(list, params){
var promises = list.map(function (item) { // return array of promises
// return the promise:
return api.getArtistAlbums(item, params)
.then(function(data) {
// return the array of album IDs:
return Array.from(data.body.items, function (alb) {
return alb.id;
});
}, function(err) {
console.error(err);
});
});
Promise.all(promises).then(function (albums) { // albums is 2D array
albums = [].concat.apply([], albums); // flatten the array
console.log(albums);
//do something with the finalized list of albums here
});
}
If you want to use data returned from a loop in node.js you have to add a little extra code to check if you're on the last iteration of the loop. Basically you're writing your own "loop complete" check and running only when that condition is true.
I went ahead and wrote a complete, runnable example so you can break it down to see how it works. The important part is to add a counter, increment it after each loop, then check for when the counter is the same length of the list that you're iterating over.
function getArtistAlbums(artist, params){
var artistAlbums = {
'Aphex Twin':['Syro', 'Drukqs'],
'Metallica':['Kill \'Em All', 'Reload']
};
return new Promise(function (fulfill, reject){
fulfill(artistAlbums[artist]);
});
}
function getAlbumsTotal(list, params){
var listCount = 0;
for(var i = 0; i<list.length; i++){
getArtistAlbums(list[i], params)
.then(function(data) {
listCount++;
for(var alb = 0; alb<data.length; alb++){
//for(var alb = 0; alb<data.items.length; alb++){
//albums.push(data.body.items[alb].id);
albums.push(data[alb]);
}
// print out album list at the end of our loop
if(listCount == list.length){
console.log(albums);
}
}, function(err) {
console.error(err);
});
}
// prints out too early because of async nature of node.js
//console.log(albums);
}
var listOfArtists = ['Aphex Twin', 'Metallica'];
var albums = [];
getAlbumsTotal(listOfArtists, 'dummy params');
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With