I am trying to return values from my REST request and in the success handler I am returning all the values as a deferred promise but when i run the landingPage function I can't resolve the deferred promise. It gives me .then is undefined. I am using Unirest library for HTTP requests and Q for Promises.
Is it because the return in the request callback returned the data earlier before the server could fetch the data and hence it didn't returned the deferred object or it is something else.
Below is the code:
var landingPage = function (dir) {
Uni.get('https://'+getDirectory(dir).url)
.header('Content-Type','text/html')
.end(function (lp_data) {
var deferred = Q.defer();
if (lp_data.raw_body) {
var $ = cheerio.load(lp_data.raw_body),
cn_europe = $('#location_europe .list-countries li'),
cn_asia = $('#content-location_asia_pacific li'),
cn_north_america = $('#content-location_north_america li');
// Get Europe Company List
var europe = [];
for (var i = 0; i < cn_europe.length; i++) {
var country_name = $(cn_europe[i]).find('a').html().split('(')[0].trim(),
esomar_url = 'https://' + getDirectory(dir).url + '/' + $(cn_europe[i]).find('a').attr('href');
europe.push({country_name: country_name, esomar_url: esomar_url});
}
return europe.length ? deferred.resolve(europe) : deferred.reject(err);
}
});
};
var tests = {
t1: function (dir) {
landingPage(dir)
.then(function (r) {
console.log(r);
})
}
}
Your main function landingPage is not returning a value. The code inside it produces a promise, but you should also return it.
As you want to return the promise held by deferred, you should make deferred a variable that is defined in the outer function scope, so you can return the promise defined by it (deferred.promise):
var landingPage = function (dir) {
var deferred = Q.defer(); // *** define here
Uni.get('https://'+getDirectory(dir).url)
.header('Content-Type','text/html')
.end(function (lp_data) {
var europe = []; // *** define here, before the `if`
if (lp_data.raw_body) {
var $ = cheerio.load(lp_data.raw_body),
cn_europe = $('#location_europe .list-countries li'),
cn_asia = $('#content-location_asia_pacific li'),
cn_north_america = $('#content-location_north_america li');
// Get Europe Company List
for (var i = 0; i < cn_europe.length; i++) {
var country_name = $(cn_europe[i]).find('a').html().split('(')[0].trim(),
esomar_url = 'https://' + getDirectory(dir).url + '/' + $(cn_europe[i]).find('a').attr('href');
europe.push({country_name: country_name, esomar_url: esomar_url});
}
}
// *** Move this out of the above `if`, so you always resolve/reject:
return europe.length ? deferred.resolve(europe) : deferred.reject(err);
});
// *** return the promise (it will not be resolved yet, but that is the idea):
return deferred.promise;
}
However, there is a request to support promises in Unirest (see here), which allows you to do this:
var landingPage = function (dir) {
// return the promise that will now come from this chain:
return Uni.get('https://'+getDirectory(dir).url)
.header('Content-Type','text/html')
.end()
.exec() // turn request object to a promise
.then(function (lp_data) {
var europe = []; // *** define here, before the `if`
if (lp_data.raw_body) {
var $ = cheerio.load(lp_data.raw_body),
cn_europe = $('#location_europe .list-countries li'),
cn_asia = $('#content-location_asia_pacific li'),
cn_north_america = $('#content-location_north_america li');
// Get Europe Company List
for (var i = 0; i < cn_europe.length; i++) {
var country_name = $(cn_europe[i]).find('a').html().split('(')[0].trim(),
esomar_url = 'https://' + getDirectory(dir).url + '/' + $(cn_europe[i]).find('a').attr('href');
europe.push({country_name: country_name, esomar_url: esomar_url});
}
}
// *** Return the data. Throw an error if to be rejected
if (!europe.length) throw "no data!!";
return europe;
});
}
This way you do not have to do the conversion to a promise yourself with a deferred object.
Related
I have this multiple promise request that needs to be resolved before continuing next set of requests.
If the printItemList length is 1, it works fine. But the problem starts hapenning when its more than 2.
The idea is to fire print_label once the previous print_label is resolved successfully. Right now its getting fired immediately and not waiting for previous request to finish.
Is there anything I'm doing wrong here.
Any help on this would be greatly appreciated.
function print_batch(printItemList){
var chain = $.when();
var arr = [];
for(var batch_size = 0; batch_size < printItemList.length; batch_size ++){
(function(i){
chain = chain.then(function() {
$.when.apply($, arr).done(function(){
arr = print_label(printItemList[i]);
});
});
})(batch_size);
}
}
function print_label(selectedRow) {
var d = $.Deferred();
var chain = $.when();
var arr = [];
var request = buildLabel(selectedRow);
var noOfLabel = parseInt(selectedRow.labelCount);
var url = 'API_URL';
var epos = new epson.ePOSPrint(url);
for (var count = 0; count < noOfLabel; count++) {
(function(i){
chain = chain.then(function() {
var def = sendRequest(selectedRow, epos, request);
arr.push(def);
return def;
});
})(count);
}
return arr;
}
function sendRequest(selectedRow, epos, request){
var deferred = $.Deferred();
epos.send(request);
epos.onreceive = function(res) {
return deferred.resolve(res);
}
epos.onerror = function(err) {
return deferred.reject();
}
return deferred.promise();
}
function print_batch(printItemList){
//create recursive method
function print_next_label(index) {
//if we have not reached the end of the list, do more work
if (index < printItemList.length) {
//call the method, wait for all the promises to finish
$.when.apply($, print_label(printItemList[index])).done(function(){
//now that they are done, try to print the next label
print_next_label(++index);
});
}
}
//start the recursive method loop with index 0
print_next_label(0);
}
The return Promise.all([photoArray]) returns an empty array, seemingly not waiting for the callFB to return its promise that then pushes into the array.
I am not sure what I am doing wrong but am relatively new to Promises with for loops and Ifs.
I am not sure exactly if I am using the correct number of Promises but I seem to not be able to get the 3rd tier Promise.all to wait for the for loop to actually finish (in this scenario, the for loop has to look through many item so this is causing an issue where it is not triggering callFeedback for all the items it should before context.done() gets called.
I have tried using Q.all also for the Promise.all([photoArray]) but have been unable to get that working.
module.exports = function (context, myBlob) {
var res = myBlob
var promiseResolved = checkPhoto(res,context);
var promiseResolved2 = checkVideo(res,context);
Promise.all([promiseResolved, promiseResolved2]).then(function(results){
context.log(results[0], results[1]);
// context.done();
});
});
};
};
function checkPhoto(res, context){
return new Promise((resolve, reject) => {
if (res.photos.length > 0) {
var photoArray = [];
for (var j = 0; j < res.photos.length; j++) {
if (res.photos[j].feedbackId !== null){
var feedbackId = res.photos[j].feedbackId;
var callFB = callFeedback(context, feedbackId);
Promise.all([callFB]).then(function(results){
photoArray.push(results[0]);
});
} else {
photoArray.push("Photo " + j + " has no feedback");
}
}
return Promise.all([photoArray]).then(function(results){
context.log("end results: " + results);
resolve(photoArray);
});
} else {
resolve('No photos');
}
})
}
function checkVideo(res, context){
return new Promise((resolve, reject) => {
same as checkPhoto
})
}
function callFeedback(context, feedbackId) {
return new Promise((resolve, reject) => {
var requestUrl = url.parse( URL );
var requestBody = {
"id": feedbackId
};
// send message to httptrigger to message bot
var body = JSON.stringify( requestBody );
const requestOptions = {
standard
};
var request = https.request(requestOptions, function(res) {
var data ="";
res.on('data', function (chunk) {
data += chunk
// context.log('Data: ' + data)
});
res.on('end', function () {
resolve("callFeedback: " + true);
})
}).on('error', function(error) {
});
request.write(body);
request.end();
})
}
The code suffers from promise construction antipattern. If there's already a promise (Promise.all(...)), there is never a need to create a new one.
Wrong behaviour is caused by that Promise.all(...).then(...) promise isn't chained. Errors aren't handled and photoArray.push(results[0]) causes race conditions because it is evaluated later than Promise.all([photoArray])....
In case things should be processed in parallel:
function checkPhoto(res, context){
if (res.photos.length > 0) {
var photoArray = [];
for (var j = 0; j < res.photos.length; j++) {
if (res.photos[j].feedbackId !== null){
var feedbackId = res.photos[j].feedbackId;
var callFB = callFeedback(context, feedbackId);
// likely no need to wait for callFB result
// and no need for Promise.all
photoArray.push(callFB);
} else {
photoArray.push("Photo " + j + " has no feedback");
}
}
return Promise.all(photoArray); // not [photoArray]
} else {
return 'No photos';
};
}
callFB promises don't depend on each other and thus can safely be resolved concurrently. This allows to process requests faster.
Promise.all serves a good purpose only if it's used to resolve promises in parallel, while the original code tried to resolve the results (results[0]).
In case things should be processed in series the function benefits from async..await:
async function checkPhoto(res, context){
if (res.photos.length > 0) {
var photoArray = [];
for (var j = 0; j < res.photos.length; j++) {
if (res.photos[j].feedbackId !== null){
var feedbackId = res.photos[j].feedbackId;
const callFBResult = await callFeedback(context, feedbackId);
// no need for Promise.all
photoArray.push(callFBResult);
} else {
photoArray.push("Photo " + j + " has no feedback");
}
}
return photoArray; // no need for Promise.all, the array contains results
} else {
return 'No photos';
};
}
Add try..catch to taste.
I have an array of tags which may contain up to 3 items.
Next I pass tags into my getTagQuotes function and am trying to set a promise variable to it. Right now I'm getting promise is undefined.
// If there are tags, the wait for promise here:
if (tags.length > 0) {
var promise = getTagQuotes(tags).then(function() {
console.log('promise =',promise);
for (var i=0; i<tweetArrayObjsContainer.length; i++) {
chartObj.chartData.push(tweetArrayObjsContainer[i]);
}
chartDirective = ScopeFactory.getScope('chart');
chartDirective.nvd3.drawChart(chartObj.chartData);
});
}
else {
chartDirective = ScopeFactory.getScope('chart');
chartDirective.nvd3.drawChart(chartObj.chartData);
}
^ The goal above is to make sure that arrays in tweetArrayObjsContainer have been filled and pushed into chartObj.chartData before I draw my nvd3 chart.
Below is my getTagQuotes function which does another loop through the tags (up to 3) and calls another service GetTweetVolFactory.returnTweetVol which makes the actual API call to retrieve data.
I have code which checks if the we're on the final loop step, then calls the formatTagData function.
Inside formatTagData is where I fill the tweetArrayObjsContainer.
// Check for and GET tag data:
////////////////////////////////////////////////////////////////////
function getTagQuotes(tags) {
console.log('getTagQuotes called with',tags);
var deferred = $q.defer(); // <- setting $q.defer here
var url = 'app/api/social/twitter/volume/';
for (var i=0; i<tags.length; i++) {
var loopStep = i;
rawTagData = [];
GetTweetVolFactory.returnTweetVol(url+tags[i].term_id)
.success(function(data, status, headers, config) {
rawTagData.push(data);
if (loopStep === (rawTagData.length - 1)) {
// formatTagData fills the tweetArrayObjsContainer with data:
formatTagData(rawTagData);
deferred.resolve(); // <-- last step, so resolve
return deferred.promise;
}
})
.error(function(data, status) {
return 'error in returning tweet data';
});
}
function formatTagData(rawData) {
tweetArrayObjsContainer = [];
for (var i=0; i<rawData.length; i++) {
var data_array = [];
var loopNum = i;
_(rawData[i].frequency_counts).reverse().value();
for (var j=0; j<rawData[loopNum].frequency_counts.length; j++) {
var data_obj = {};
rawData[loopNum].frequency_counts[j].start_epoch = addZeroes(rawData[loopNum].frequency_counts[j].start_epoch);
data_obj.x = rawData[loopNum].frequency_counts[j].start_epoch;
data_obj.y = rawData[loopNum].frequency_counts[j].tweets;
data_array.push(data_obj);
}
var tweetArrayObj = {
"key" : "Quantity"+(i+1),
"type" : "area",
"yAxis" : 1,
"color" : tagColorArray[i],
"values" : data_array
};
tweetArrayObjsContainer.push(tweetArrayObj);
}
}
}
Your getTagQuotes function should return a promise with the $q service, check it out.
It should look like this:
function getTagQuotes(tags) {
var deferred = $q.defer();
(...)
// When the action is finished here, call resolve:
deferred.resolve();
(...)
// And in the end, return the promise
return deferred.promise;
}
And finally, in your main code, you'll do:
var promise = getTagQuotes(tags).then(function(){
console.log('promise =',promise);
// Fill chartObj with tweet data arrays
(...)
});
This function has no return statement.
function getTagQuotes(tags)
I have this readLines function to parse line by line that called from:
var fs = require('fs');
var Q = require('q');
Q.all(readLines(fs.createReadStream("/tmp/test.txt"), console.log)).then(function () {
console.log('Done');
};
function readLines(input, func) {
var remaining = '';
input.on('data', function (data) {
remaining += data;
var index = remaining.indexOf('\n');
while (index > -1) {
var line = remaining.substring(0, index);
remaining = remaining.substring(index + 1);
func(line);
index = remaining.indexOf('\n');
}
});
input.on('end', function () {
if (remaining.length > 0) {
func(remaining);
}
});
};
Could anyone help why I never got "Done"? Any tutorial to understand how Promises work?
This will work better for you. Please read the comments in the code - it's actually only a few extra lines, and that was including adding error handing.
var fs = require('fs');
var Q = require('q');
// you don't need Q.all unless you are looking for it to resolve multiple promises.
// Since readlines returns a promise (a thenable), you can just then() it.
readLines(fs.createReadStream("./vow.js"), console.log).then(function (x) {
console.log('Done', x);
});
function readLines(input, func) {
// you need to create your own deferred in this case - use Q.defer()
var deferred = Q.defer();
var remaining = '';
input.on('data', function(data) {
remaining += data;
var index = remaining.indexOf('\n');
while (index > -1) {
var line = remaining.substring(0, index);
remaining = remaining.substring(index + 1);
func(line);
index = remaining.indexOf('\n');
}
});
input.on('end', function() {
if (remaining.length > 0) {
func(remaining);
console.log('done');
}
// since you're done, you would resolve the promise, passing back the
// thing that would be passed to the next part of the chain, e.g. .then()
deferred.resolve("Wouldn't you want to return something to 'then' with?");
});
input.on('error', function() {
console.log('bother');
// if you have an error, you reject - passing an error that could be
// be caught by .catch() or .fail()
deferred.reject(new Error('Regrettable news - an error occured.'));
});
// you return the promise from the deferred - not the deferred itself
return deferred.promise;
};
Here are the cloud functions namely 'batchReq1' and batchPromises.
In any case, if I know the exact number of promises pushed (Consider the size of results to be '2' in function batchPromises(results)) and executed through when(), I can handle the success response by passing that number of result parameters (In the below example request1, request2) in successCallBack of .then().
If I have to process the number of promises pushed to .when() dynamically, then, how can we handle this in SuccessCallBack? Unlike earlier scenario, we can't expect fixed number of results in the then method (batchPromises(results).then(function (result1, result2) {....)
batchReq1
Parse.Cloud.define("batchReq1", function (request, response) {
var results = request.params.imageArray;
batchPromises(results).then(function (result1, result2) {
console.log("Final Result:: Inside Success");
console.log("Final Result:: Inside Success result 1::::"+result1);
console.log("Final Result:: Inside Success result 2::::"+result2);
response.success();
}
// batchPromises(results).then(function (arraySuccess) {
//
// console.log("Final Result:: Inside Success");
// console.log("Final Result:: Inside Success:: Length:: "+arraySuccess.length);
// //Fetch all responses from promises and display
// var _ = require('underscore.js');
// _.each(arraySuccess, function (result) {
//
// console.log("Final Result:: " + result)
//
// });
//
//
// response.success();
//
// }
, function (error) {
console.log("Final Result:: Inside Error");
response.error(error);
});
});
batchPromises
function batchPromises(results) {
var promise = Parse.Promise.as();
var promises = [];
var increment = 0;
var isFromParallelExecution = false;
var _ = require('underscore.js');
_.each(results, function (result) {
var tempPromise = Parse.Promise.as("Promise Resolved ");
promises.push(tempPromise);
}
promise = promise.then(function () {
return Parse.Promise.when(promises);
});
}
increment++;
});
return promise;
}
this is how i handle this...
Parse.Cloud.define("xxx", function(request, response)
{
var channels = ["channel1", "channel2", "channel3", "channel4", "channel5"];
var queries = new Array();
for (var i = 0; i < channels.length; i++)
{
var query = new Parse.Query(channels[i]);
queries.push(query.find());
}
Parse.Promise.when(queries).then(function()
{
var msg = "";
for (var j = 0; j < arguments.length; j++)
{
for (var k = 0; k < arguments[j].length; k++)
{
var object = arguments[j][k];
msg = msg + " " + object.get('somefield');
}
}
response.success(msg);
});
});
Either you just use the arguments object to loop over the results, or you build your arraySuccess without when - it doesn't make much sense here anyway as you batch the requests (executing them sequentially), instead of executing them in parallel:
function batchPromises(tasks) {
var _ = require('underscore.js');
_.reduce(tasks, function (promise, task) {
return promise.then(function(resultArr) {
var tempPromise = Parse.Promise.as("Promise Resolved for "+taks);
return tempPromise.then(function(taskResult) {
resultArr.push(taskResult);
return resultArr;
});
});
}, Parse.Promise.as([]));
}
If you actually wanted to execute them in parallel, use a simple
function batchPromises(tasks) {
var _ = require('underscore.js');
return Parse.Promise.when(_.map(tasks, function (task) {
return Parse.Promise.as("Promise Resolved for "+taks);
}).then(function() {
return [].slice.call(arguments);
});
}