I'm trying to do a return of JSON object using a Bluebird's Promise.mapSeries/Promise.map within a Promise.mapSeries/Promise.map but I am unable to return them correctly. The function is as follows:
function getMovieDetails(link){
return new Promise(function(resolve, reject) {
request(link, function(error, response, body){
var request = Promise.promisifyAll(require("request"), {multiArgs: true});
var $ = cheerio.load(body);
var movieYears = "Years";
var movieYearLinks = [];
movieYearLinks.each(function(index, item) {
var movieYear = $(item).text();
var movieYearLink = $(item).attr("href");
movieYearLinks.push(movieYearLink);
});
Promise.mapSeries(movieYearLinks, function(url) {
return request.getAsync(url).spread(function(response,body) {
var $ = cheerio.load(body);
var movie = {};
var year = "YEAR".text();
movie["year"] = year;
var movieActorsArray = [];
movieActors.each(function(index, item){
var movieActor = $(item).text();
movieActorsArray.push(movieActor);
});
movie["movieActors"] = movieActorsArray;
var recommendedMovies = //SOME ROWS;
var recommendedMoviesLinks = [];
recommendedMovies.each(function(jndex, jtem){
var recommendedRowObject = {};
var recommendedText = .text();
var recommendedLink = .attr("href");
recommendedRowObject["recommendedText"] = recommendedText
recommendedRowObject["recommendedLink"] = recommendedLink
recommendedMoviesLinks.push(recommendedLink);
});
Promise.mapSeries(recommendedMoviesLinks, function(url) {
return request.getAsync(url).spread(function(response,body) {
var $ = cheerio.load(body);
var obj = {};
// GET SOME OTHER DESCRIPTION FROM THE RECOMMENDED MOVIE LINK
return obj;
});
}).then(function(results) {
// results here returns an array of recommended movie links objects.
}).catch(function(err) {
});
return main;
});
}).then(function(results) {
// results here returns an array of movies
// I want to be able to link each movie with its recommended movies.
}).catch(function(err) {
console.log("Big Error " + err);
});
});
});
}
Some explanation of my code and context.
The home page is a page with movie years. I've looped through them to get the links of the years and put into an array movieYearLinks.
Next, I've used Promise.mapSeries to get some basic info on the movies. Within that basic info, there is are recommended movie link listed in a table. I've looped them through and put them into an array, recommendedMoviesLinks.
After which, I'm going in to grab some other recommended movie (recommendedMovieObject) info before.
I want to create a JSON object with all these info. The JSON object should be something like
{ movieYear: 2017, movieLink: ..., recommendedMovies (This is an array
of recommendedMovieObjects): }
I'm open to any solution to achieve this. Thank you in advance.
You don't need to nest promises, the whole point of promises is not having to nest callbacks. A new promise need to either be rejected with an error or resolved with a value. Your code should be something like :
let getMovieDetails = function(link){
return new Promise(function(resolve,reject){
request(link,function(err,response,body){
err ? reject(err):resolve(body)
}
}).then(function(body){
// do some cherio stuff
return dataList
}).then(function(dataList){
return Promise.all(dataList.map(function(d){return // a promise of d})
})
}
let finalData = getMovieDetails(link).then(function(data){
//do some computation
}).catch(function(e){ // handle errors })
Depending on your version of node there is no need for bluebird at all. Javascript supports promises.
Related
The code below does not throw a syntax error, but the THEN statement which renders the data on the datatable is executing first, before all lists have been queried
What is the intention of this code, there are many sharepoint lists with similar names: Bill Cycles, Bill Cycles Archive1....N.
I need to query all those lists for a specific query, and concatenate the results of all of them in the result variable and then use that variable with datatables plugin to render, but as explain on the first statement, the outer .then is executeed in the beginning and inner then is executed later when the page is already rendered.
function GetData(billCycleId, clientCode, jobCodes, engagementCode) {
var deferred = $q.defer();
var enhanceFunctions = [
function(searchResultRow) {
return spService.AddHyperLinkOnFields(searchResultRow, config.HyperLinks);
},
function(searchResultRow) {
return spService.AddPresenceOnFields(searchResultRow, config.UserFields);
},
function(searchResultRow) {
return spService.FormatDateFields(searchResultRow, config.DateFields, generalConfig.DateTimeFormat);
},
function(searchResultRow) {
return spService.AddImageMapping(searchResultRow, config.ImageFields);
},
function(searchResultRow) {
return spService.FormatNumberFields(searchResultRow, config.NumberFields);
}
];
var selectProperties = spService.TransformFieldsToSelectProperties(config.Fields);
var extendedSelectProperties = selectProperties.slice(); // copy array
var hyperLinkedProperties = spService.TransformFieldsToSelectProperties(config.HyperLinks)
extendedSelectProperties = extendedSelectProperties.concat(hyperLinkedProperties);
var result =[];
spService.GetAllListsFromWeb()
.then(function (lists) {
var listEnumerator = lists.getEnumerator();
return $q.all(
(function(){
var promises = [];
while (listEnumerator.moveNext()) {
var oList = listEnumerator.get_current();
var title = oList.get_title();
var id = oList.get_id();
if (title.indexOf('Bill Cycles') !== -1) {
// Get data from SP !!! this is also async and returns a promise
// add the promise to promises array and wait for all to finish
// look above in Promise.all
promises.push(
GetRelatedBillCyclesFromList(
id,
extendedSelectProperties,
billCycleId,
clientCode,
jobCodes,
engagementCode,
enhanceFunctions
)
.then(function (data) {
var trimmedData =
spService
.SpSearchQuery
.TrimSearchResultsToSelectProperties(
data,
selectProperties
);
trimmedData.forEach(function(item){ // loop over source array
result.push(item); //append to result array
});
})
);
}
}
//return promises
})() //IIFE returning an array of promises
);
})
.then( //The promise is used to execute something after all promises are resolved, but the results are build in the result array, no need to use data
function(data){
//var resultadata = data;
var dataTable = $(tableSelector).DataTable();
dataTable.clear().rows.add(result).columns.adjust().draw(); // Resize columns based on new data sizes
vm.ValidDataLoaded = true;
}
);
}
You pass undefined to $q.all , because //return promises is commented out. Uncommenting that should fix your issue.
I have a problem with understanding Promises syntax.
So, what I am trying to do:
getPosts() gets some data from a DB then, I want to get some metadata for each row with another promise call, addMetadata(). Then, once all the metadata is fetched, I want to console it out.
See my attempt below:
var getPosts = function(){
return new Promise(function(resolve, reject){
postModel.find()
.exec(function(err, posts) {
resolve(posts);
);
});
};
var addMetadata = function(posts){
var options = {
host: 'localhost',
port: 3000,
path: '',
method: 'GET'
};
var postPromises = posts.map(function(post) {
return new Promise(function(resolve) {
options.path = '/api/user?id=' + post.profileID;
var req = http.get(options, function(res) {
var bodyChunks = [];
res.on('data', function(chunk) {
bodyChunks.push(chunk);
}).on('end', function() {
var body = Buffer.concat(bodyChunks);
var parsedBody = JSON.parse(body);
post.fullname = parsedBody.user.fullname;
post.profilePic = parsedBody.user.profilePic;
// resolve the promise with the updated post
resolve(post);
});
});
});
});
// Is this the right place to put Promise.all???
Promise.all(postPromises)
.then(function(posts) {
//What should I put here
});
};
getPosts()
.then(function(posts){
return addMetadata(posts);
})
.then(function(posts){//I get a little lost here
console.log();//posts is undefined
});
Of course, my understanding is wrong but I thought I was going the right way. Can someone please guide me to the right direction?
Thanks
Change
// Is this the right place to put Promise.all???
Promise.all(postPromises)
.then(function (posts) {
//What should I put here
});
into
// Is this the right place to put Promise.all???
return Promise.all(postPromises);
This way your addMetadata function will return Promise that resolve when all promises from postPromises resolves or reject if any of postPromises rejects.
The key point to understand the async concept of it and what time the content is available.
Reading this will help to put you in the right direction.
For instance:
var promise = new Promise(function(resolve, reject) {
resolve(1);
});
promise
.then(function(val) {
console.log(val); // 1
return val + 2;
})
.then(function(val) {
console.log(val); // 3
})
After as per your scenario, in order to have all the metadata Promise.all is the way to go.
Promise.all(arrayOfPromises).then(function(arrayOfResults) {
// One result per each promise of AddMetadata
})
What you wanna do here, if I am correct, is called streams, as you wanna call multiple paralel promises as your concept of looping through list of posts using map is not going to work this way
Take a look at this short video introducing streams Streams - FunFunFunction, he is using library for workin with streams called Baconjs
Here is a short example on streams
const stupidNumberStream = {
each: (callback) => {
setTimeout( () => callback(1), 3000 )
setTimeout( () => callback(2), 2000 )
setTimeout( () => callback(3), 1000 )
}
}
stupidNumberStream.each(console.log)
Your getPosts function is good in the sense that its only job is to promisfy the database find. (Though, I think if it's mongo, the exec produces a promise for you).
Your addMetadataToAPost is less good, because it mixes up processing an array of posts and "promisifying" the http.get. Use the same pattern you applied correctly in the first function and return a promise to do a single get and add metadata. (It would be even better to just wrap the get, which you can reuse, and build a simple add-metadata function that returns - rather than creates - a promise)
// renamed pedantically
var addMetadataToAPost = function(post) {
return new Promise(function(resolve) {
options.path = '/api/user?id=' + post.profileID;
var req = http.get(options, function(res) {
var bodyChunks = [];
res.on('data', function(chunk) {
bodyChunks.push(chunk);
}).on('end', function() {
var body = Buffer.concat(bodyChunks);
var parsedBody = JSON.parse(body);
post.fullname = parsedBody.user.fullname;
post.profilePic = parsedBody.user.profilePic;
// resolve the promise with the updated post
resolve(post);
});
});
});
}
Now your batching function is simple:
// also renamed pedantically
var addMetadataToAllPosts = function(posts){
var postPromises = posts.map(function(post) {
return addMetadataToAPost(post)
})
return Promise.all(postPromises)
};
Your original code should work...
getPosts().then(function(posts){
return addMetadataToAllPosts(posts);
})
.then(function(posts){
console.log(posts);//posts should be an array of posts with metadata added
});
I'm having trouble figuring out why my data is not being push into my new array, "results". newArr[0].mscd.g[i] is a list of several objects.
var axios = require('axios');
var moment = require('moment');
var _ = require('lodash');
var getData = function() {
return getNBASchedule().then(function(payload) {
return filterByMonth('January', payload);
}).then(function(result) {
return result
});
}
....
getData grabs the data from baseURL and returns a list of objects.
var getMonthlySchedule = function(data){
var results = [];
var newArr = data.slice(0, data.length);
for (var i = 0; i <= newArr[0].mscd.g.length; i++) {
if (newArr[0].mscd.g[i].v.tid === 1610612744 || newArr[0].mscd.g[i].h.tid === 1610612744) {
results.push(newArr[0].mscd.g[i]); <---- //does not seem to work
// however if I were to console.log(newArr[0].mscd.g[i],
// I would see the list of objects)
}
}
return results; <-- //when i console at this point here, it is blank
};
var getSchedule = function () {
return getData().then(function(pl) {
return getMonthlySchedule(pl)
})
};
var monthlyResults = function() {
return getSchedule().then(function(r) {
console.log("result", r)
return r
});
};
monthlyResults();
You don't know when getSchedule() is done unless you use a .then() handler on it.
getSchedule().then(function(data) {
// in here results are valid
});
// here results are not yet valid
You are probably trying to look at your higher scoped results BEFORE the async operation has finished. You HAVE to use .then() so you know when the operation is done and the data is valid.
Your code should simplify as follows :
var getData = function() {
return getNBASchedule().then(function(payload) {
return filterByMonth('January', payload);
});
}
var getMonthlySchedule = function(data) {
return data[0].mscd.g.filter(function(item) {
return item.v.tid === 1610612744 || item.h.tid === 1610612744;
});
};
var monthlyResults = function() {
return getData()
.then(getMonthlySchedule)
.then(function(r) {
console.log('result', r);
return r;
});
};
monthlyResults();
This may fix the problem. If not, then :
Check the filter test. Maybe those .tid properties are String, not Number?
Check that data[0].mscd.g is the right thing to filter.
I am trying to fetching all entries for a particular key patterns and to make the callback happen neatly, I am using Bluebird. The redis client for nodejs is node_redis for the project.
The code in redis client is -
exports.getAllRedisKeysA = function() {
var res = rclient.keysAsync("client*").then(function(data) {
// console.log(data);
}).then(function(data) {
var arrayResp = [];
for (var element in data) {
rclient.hgetallAsync(element).then(function(data) {
arrayResp.push(data);
});
};
return arrayResp;
// console.log(data);
}).catch(console.log.bind(console));
console.log(res); // gives an empty promise.
return res;
}
And this function is being called from a controller in the manner below -
var abc = rdata.getAllRedisKeysA();
// console.log(abc); // gives undefined
The console.log output inside the redis function gives an empty promise and nothing is returned to the controller.
Am I missing anything in the implementation?
Linus and Jaromanda had some real helpful comments to the question that helped me move in the right direction. I have used the below way to fetch my required data from REDIS using BlueBird Promise and here's how this needs be done.
The code below gets the required data from REDIS
exports.getRedisKeyPattern = function(pattern){
var allKeys = allKeysPattern(pattern).then(function(data){
var arrayResp = [];
var inptArr = [];
var newdata = data.slice(1)[0];
for(var i in newdata){
inptArr.push(newdata[i]);
};
return new Promise.resolve(inptArr);
});
var valuePerKey = Promise.mapSeries(allKeys, function(dt){
return getAllKeyContents(dt);
}).then(function(res){
return res;
}).catch(function(err) { console.log("Argh, broken: " + err.message);
});
return new Promise.resolve(valuePerKey);
}
function getAllKeyContents(key){
var data = rclient.hgetallAsync(key).then(function(data){
return data;
}).catch(function(err) { console.log("Argh, broken: " + err.message); });
var res = Promise.join(data, key, function(data, key){
return {
key: key,
data: data
};
});
return res;
}
From the controller, the function is called like this -
var rdata = require('../../components/redis/redis-functions');
rdata.getRedisKeyPattern("clients*").then(function(response){
return res.status(200).json(response);
});
The .js file which contains the redis functions is included into the controller file so that functions can be used.
Maybe I am overthinking this, but I puzzled by the issue I am facing. I am currently using the JavaScript S3 SDK to pull some templates down from my buckets. I am using a promise to return the results, however, the data that is returned is a bunch of objects because I am looping through a list of file paths where the objects are located. Because I am using a promise, it only returns one of the eighty or so returned objects. So my solution is to place the returned objects in an array that I can pass as my returned data for my promise. Is this something that RX would be applicable for and if so, what methods would be recommended?
var getTemplateParams = function (isoObj) {
return new Promise(function (resolve, reject) {
var params;
var paths = isoObj.filePathArr.Contents;
for (var i = 0; i < paths.length; i++) {
params = {
Bucket: isoObj.filePathArr.Name,
Key: paths[i].Key,
ResponseContentEncoding: 'utf-8',
ResponseContentType: 'ReadableStream'
}
s3.getObject(params, function (err, data) {
if (err) {
reject(err);
} else {
data['isoId'] = isoObj.isoId;
data['templateId'] = isoObj.templateId;
data['siteName'] = isoObj.siteName;
data['domain'] = isoObj.domain;
data['mainEmail'] = isoObj.mainEmail;
data['mainPhone'] = isoObj.mainPhone;
data['Name'] = isoObj.filePathArr.Name;
// The data that is returned needs to get put into an array.
resolve(data);
}
});
};
})
}
Thanks in advance for any help you can provide.
In RxJS it would look something like this:
var getTemplateParams = function (isoObj) {
//Turns this callback function into a function that returns a observable
var getObjectObservable = Rx.Observable.fromNodeCallback(s3.getObject);
//Wrap the paths into an Observable
return Rx.Observable.fromArray(isoObj.filePathArr.Contents)
//Convert the paths in to param objects
.map(function(path) {
return {
Bucket: isoObject.filePathArr.Name
Key: path.Key,
ResponseContentEncoding: 'utf-8',
ResponseContentType: 'ReadableStream'
};
})
//Have each request sent out to the server
.flatMap(getObjectObservable,
//Process each result
function(path, data) {
//Don't fully understand why you are copying all the data
//from isoObject into the data object.
data['isoId'] = isoObj.isoId;
data['templateId'] = isoObj.templateId;
data['siteName'] = isoObj.siteName;
data['domain'] = isoObj.domain;
data['mainEmail'] = isoObj.mainEmail;
data['mainPhone'] = isoObj.mainPhone;
data['Name'] = isoObj.filePathArr.Name;
return data;
})
//Flatten all the results into an Array.
.toArray();
}
I think I would do it like this.
Make each s3 request a promise and then use something Promise.all to resolve all the promises, which returns the results of the promises as an array
Just found this code I have used before. I use bluebird, but most promise frameworks should be able to do this
return Promise.all(photos.map(function(photo) {
var params = 'bucketinfo'
return s3.getSignedUrlAsync('getObject', params)
.then(function(url) {
return url;
});
}));