I think I am fundamentally misunderstanding how Promises work in Node.
I'm writing a very small Express app that I want to return a JSON of all of my Spotify playlists that contain a certain keyword. I am using spotify-web-api-node
from npm.
Spotify's API limits 50 playlists per request. I had (what I thought to be) a solution: simply increment the offset value until you no longer need to.
My functions looks like this (playlists
is declared above the route function) :
app.get('/playlists', function(req, res) {
addPlaylistsToList()
.then((data) => res.json(data))
});
function addPlaylistsToList(offset) {
if (!offset) { offset = 0; }
var limit = 50;
return new Promise((resolve, reject) => {
spotifyApi.getUserPlaylists(config.USER, { limit: limit, offset: offset })
.then(function(data) {
playlists.push(data.body.items.filter((playlist) => playlist.name.includes(config.TITLE_CONDITION)));
if (data.body.items.length == limit) {
addPlaylistsToList(offset + limit);
} else {
let result = [...new Set(playlists)];
resolve(result);
}
})
});
}
Am I unable to call resolve
here? I mean, I know I can't because I get caught in an infinite loop. Is there a "best practice" re: recursive promises? Am I misusing then
?
It's generally fine to use recursive promises, but there's a few things you're missing in your implementation. Each call to addPlaylistsToList()
creates a new promise, but only one call will create a new promise that actually resolves: the very last one. All the other created promises will be pending infinitely.
You can rewrite the function in a more typical recursive style, something like this:
function addPlaylistsToList(offset = 0) { //default parameter value to 0
var limit = 50;
return spotifyApi.getUserPlaylists(config.USER, { limit: limit, offset: offset })
.then(function(data) {
playlists.push(data.body.items.filter((playlist) => playlist.name.includes(config.TITLE_CONDITION)));
if (data.body.items.length == limit) {
return addPlaylistsToList(offset + limit); //return a promise
} else {
return [...new Set(playlists)]; //return the final value
}
});
}
(untested)
This works because returning a promise to the .then()
callback causes the .then()
to "follow" the returned promise - it will only resolve when that promise resolves, and so on. You can recursively chain promises like this, and ultimately the bottommost one will resolve with your result array, causing all the other ones to resolve with that array as well.
Also, this can be written much cleaner using async/await:
async function addPlaylistsToList(offset = 0) { //default parameter value to 0
var limit = 50;
var data = await spotifyApi.getUserPlaylists(config.USER, { limit: limit, offset: offset });
playlists.push(data.body.items.filter((playlist) => playlist.name.includes(config.TITLE_CONDITION)));
if (data.body.items.length == limit) {
return await addPlaylistsToList(offset + limit);
} else {
return [...new Set(playlists)];
}
}