Reputation: 273
I'm having problem with multiple async calls. I have three tasks i want to apply. First i get some json data from first request. Then when that request has finished I make multiple request to getMovie trailers from another service and merge that data to every object in the first response. When all that has finished then i want to write that data to json file. Simple enough but I'm failing at promises.
Here's my code
MovieService.getContent(config.url + '?key=' + config.moviekey).then(function(data) {
var movies = JSON.parse(data);
var cnt = 0;
var len = movies.length;
var results = [];
for (var i = 0; i < movies.length; i++) {
var imdbid = movies[i].ids.imdb;
(function (i, imdbid) {
MovieService.getContent(config.themoviedburl + 'tt' + imdbid + '/videos?api_key=' + config.themoviedbkey).then(function (data) {
len--;
movies[i].trailers = JSON.parse(data);
if (len === 0) {
FileService.writeToJson(JSON.stringify(movies), config.showtimesfilepath);
}
});
})(i, imdbid);
};
});
Now this works but what if one of the request in the loop would fail. Then my counter is incorrect and I would write the data to file. Could someone please help me set up similar scenario using promises. And ohh yes another pease of my code is the MovieService witch make all the requests
Upvotes: 0
Views: 274
Reputation: 707158
A common way to sequence a series of operations on an array using promises is with .reduce()
where each iteration adds onto a promise chain causing all the async operations to be sequenced:
// in sequence
MovieService.getContent(config.url + '?key=' + config.moviekey).then(function(data) {
var movies = JSON.parse(data);
return movies.reduce(function(p, movie, index) {
return p.then(function() {
var imdbid = movie.ids.imdb;
return MovieService.getContent(config.themoviedburl + 'tt' + imdbid + '/videos?api_key=' + config.themoviedbkey).then(function (data) {
movies[index].trailers = JSON.parse(data);
}, function(err) {
// handle the error here and decide what should be put into movies[index].trailers
});
});
}, Promise.resolve()).then(function() {
return FileService.writeToJson(JSON.stringify(movies), config.showtimesfilepath);
});
});
Conceptually, what this does is call movies.reduce()
and the starting value passed into .reduce()
is a resolved promise. Then each iteration through .reduce()
adds onto the promise chain with something like p = p.then(...)
. This causes all the operations to be sequenced, waiting for one to complete before invoking the next. Then, inside of that .then()
handler, it returns the MovieService.getContent()
promise so that this iteration will wait for the inner promise to complete too.
You can probably do these operations in parallel too without forcing them to be sequenced. You just need to know when they are all done and you need to keep all the data in order. That could be done like this:
// in parallel
MovieService.getContent(config.url + '?key=' + config.moviekey).then(function(data) {
var movies = JSON.parse(data);
var promises = [];
movies.forEach(function(movie, index) {
var imdbid = movie.ids.imdb;
promises.push(MovieService.getContent(config.themoviedburl + 'tt' + imdbid + '/videos?api_key=' + config.themoviedbkey).then(function (data) {
movies[index].trailers = JSON.parse(data);
}, function(err) {
// handle the error here and decide what should be put into movies[index].trailers
}));
});
Promise.all(promises).then(function() {
return FileService.writeToJson(JSON.stringify(movies), config.showtimesfilepath);
});
});
Or, using the Bluebird's promise library's helpful Promise.map()
, here's a shorter parallel version
// use Bluebird's Promise.map() to run in parallel
MovieService.getContent(config.url + '?key=' + config.moviekey).then(function(data) {
var movies = JSON.parse(data);
Promise.map(movies, function(movie, index) {
var imdbid = movie.ids.imdb;
return MovieService.getContent(config.themoviedburl + 'tt' + imdbid + '/videos?api_key=' + config.themoviedbkey).then(function (data) {
movies[index].trailers = JSON.parse(data);
}, function(err) {
// handle the error here and decide what should be put into movies[index].trailers
});
}).then(function() {
return FileService.writeToJson(JSON.stringify(movies), config.showtimesfilepath);
});
});
If you want the process to continue even if any given request fails, then you have to describe what you want to happen in that case and you can accomplish that by handling an error on .getContent()
so that it always returns a resolved promise.
Upvotes: 2
Reputation: 2456
Instead of chaining the promises, you can just run the promises asynchronously, and when all promises are resolved whether or not any one of them fails, you can write to JSON using your FileService. This is done by using Promise.all()
.
For example,
MovieService.getContent(config.url + '?key=' + config.moviekey).then(function (data) {
var movies = JSON.parse(data);
var movieDataPromises = [];
movies.forEach(function (movie) {
var imdbid = movie.ids.imdb;
movieDataPromises.push(
MovieService.getContent(config.themoviedburl + 'tt' + imdbid + '/videos?api_key=' + config.themoviedbkey).then(function (trailerData) {
movie.trailers = JSON.parse(trailerData);
}).catch(function () {});
);
});
Promise.all(movieDataPromises).then(function () {
FileService.writeToJson(JSON.stringify(movies), config.showtimesfilepath);
});
});
The reason why there is a catch with an empty callback body when retrieving the trailer info is because we want to prevent the Promise.all()
from executing its fail-fast behavior.
EDIT: avoid using the promise constructor antipattern.
Upvotes: 0