I've been trying to understand promises for the server side for the last few days, but I've yet to find anything to work. Bluebird however seems to be a good alternative. Following the API I've tried to test it out but I get an error of: "Cannot call method 'then' of undefined". I looked for similar errors but I couldn't make mine work similarly.
This is my attempt with bluebird so far:
var Promise = require('bluebird')
var cheerio = require('cheerio');
var request = require('request');
// do I need this?
// var request = Promise.promisify(require('request'));
// Promise.promisifyAll(request);
function getA() {
$(path).each(function(i,e){
link = $(this).attr("href");
getB(...).then(function(urls) {
console.log('type of urls: ' + typeof(urls) + urls);
// getC();
}).then(function(urls) {
// getD();
});
});
}
function getB() {
return new Promise(function(fullfill, reject) {
request(selectedShow, function (err, resp, page) {
if (!err && resp.statusCode == 200) {
var $ = cheerio.load(page);
$(path).each(function(i,e){
stuff.push($(this).text());
}
}); // end each loop
} // end error and status check
fullfill(stuff);
}); // end request
}
function getC() {
// use 'stuff' array to make requests and fill another array
}
function getD() {
// use the arrays to build an object
}
First thing to note, you should return the promise in function getB.
Second, you're fulfilling the promise too early. Rather you should fulfill/reject after you get a response from your request:
function getB() {
return new Promise(function(fullfill, reject) {
request(selectedShow, function(err, resp, page) {
// at this point you have a response could be error/success
if (!err && resp.statusCode == 200) {
var $ = cheerio.load(page);
$(path).each(function(i, e) {
stuff.push($(this).text());
});
// fulfill here, since success
fullfill(stuff);
} else {
reject("error"); // usually reject on error
}
});
// TOO EARLY, COS YOU DON'T HAVE A RESPONSE YET!
// fullfill(stuff);
}); // end request
}
I don't expect this answer to absolutely resolve your issue, but it should help though. Feel free to ask for additional direction after applying the change above.
To answer OP's questions in the comments to this answer
You have to modify your implementation of getA to the following effect:
function getA() {
$(path).each(function(i,e){
link = $(this).attr("href");
getB(...).then(function(urls) {
// NOTE: the `urls` parameter will have same value as the
// `stuff` used in calling `fulfill(stuff)`
console.log('type of urls: ' + typeof(urls) + urls);
// getC();
// you may also call getD() here
}).then(function(urls) {
// this additional `.then()` chain is not necessary as you could
// have done what it is you wanted to do here inside the first `then(...)`
// getD();
});
});
}
Now about the order of execution; putting everything together - your original code snippet and applying changes I made to it in this answer...
When you call getA(), that will in turn call getB() and after getB() executes successfully then getC() and getD() will be executed respectively. In summary, the order is getA() --> getB() --if getB() executed with a successful outcome--> getC() --> getC().
Now let's define two other functions getWhatever and ohNo:
function getWhatever(){
console.log("whateeveeer!");
}
function ohNo(){
console.log("eeeeew!");
}
We will modify getA a bit:
function getA() {
$(path).each(function(i,e){
link = $(this).attr("href");
getB(...).then(function(urls) {
console.log('type of urls: ' + typeof(urls) + urls);
getC();
}).then(function(urls) {
getD();
}).catch(function(){
// you only reach this point if the outcome of getB was an error
ohNo();
});
getWhatever();
});
}
With this new change to getA here are the orders of execution for different outcomes:
when outcome of getB is success:
getA() --> getB() --> getWhatever() --> getC()--> getD()
when outcome of getB is success:
getA() --> getB() --> getWhatever() --> ohNo()
I hope all your concerns are now addressed fully.
Related
I have a service below. I will call this service every time when I open a model and when I close the model and then open another one the previous values are getting reflected and in this case I want to cancel the promise every time I close the model.
I have tried the following code,
Model closing.js
$scope.closeButton = function() {
DetailDataSvc.storeDefer().resolve()
}
My Service, (DetailDataSvc)
self.storeDefer = function() {
return self.deferReturn;
};
self.getDetailReportData = function(postData, functionName) {
var promises = {};
var d = $q.defer(),
metricDataType;
self.deferReturn = $q.defer();
promises = {
detailReport: metricDataType,
recommendedMetrics: DataSvc.getData(_logPrefix + functionName, recommendedMetricUrl),
metricInfo: DataSvc.getData(_logPrefix + functionName, metricInfoUrl)
};
$q.all(promises).then(function(res) {
$log.debug(_logPrefix + 'getDetailReportData(). Called from %s. $q.all Response (raw): ', functionName, res);
else {
if (response && !_.isEmpty(_.get(response, 'largeCard.chartData.dataValues.rows')) && response.overlayEnabled) {
self.getMetricOverLay(pdata, functionName).then(function(overlayData) {
response.largeCard.chartData.overlay = overlayData;
d.resolve(response);
}, function(msg, code) {
d.reject(msg);
$log.error(_logPrefix + 'getDetailReportData(). Error code: %s. Error: ', code, msg);
});
} else {
d.resolve(response);
}
}
}, function(msg, code) {
d.reject(msg);
$log.error(_logPrefix + 'getDetailReportData(). Error code: %s. Error: ', code, msg);
});
return d.promise;
};
Can anyone please help me whether the process I followed is the right one.
What you have attempted could be made to work but it's best fixed by racing the promise returned by $q.all() against a rejectable Deferred (ie. a Deferred, of which a reference is kept to its reject method), thus avoiding the deferred anti-pattern.
self.getDetailReportData = function(postData, functionName) {
var metricDataType = ......; // ???
var d = $q.defer();
// cancel previous
if(self.cancelDetailReport) {
self.cancelDetailReport(new Error('previous getDetailReportData() cancelled'));
}
// keep a reference to the deferred's reject method for next time round.
self.cancelDetailReport = d.reject;
var promises = {
'detailReport': metricDataType,
'recommendedMetrics': DataSvc.getData(_logPrefix + functionName, recommendedMetricUrl),
'metricInfo': DataSvc.getData(_logPrefix + functionName, metricInfoUrl)
};
// Race aggregated `promises` against `d.promise`, thus providing the required cancellation effect.
return $q.race([$q.all(promises), d.promise])
.then(function(response) {
// arrive here only if all promises resolve and d.reject() has not been called.
$log.debug(_logPrefix + 'getDetailReportData(). Called from %s. $q.all Response (raw): ', functionName, response);
if (response && !_.isEmpty(_.get(response, 'largeCard.chartData.dataValues.rows')) && response.overlayEnabled) {
return self.getMetricOverLay(pdata, functionName)
.then(function(overlayData) {
response.largeCard.chartData.overlay = overlayData;
return response;
});
} else {
return response;
}
})
.catch(function(msg, code) { // signature?
// all error cases including cancellation end up here.
var message = _logPrefix + `getDetailReportData(). Error: (${code}): ${msg}`; // or similar
$log.error(message);
throw new Error(message); // see https://stackoverflow.com/a/42250798/3478010
});
};
Notes:
$q.race() is transparent to whichever promise wins the race, and opaque to the other. So, if the d is rejected before the promise returned by $q.all() settles, then d will win out; response handling will not happen and d's rejection will fall through to the .catch() clause. Alternatively, if the promise returned by $q.all(promises) wins out then flow will follow that promise's success path (ie response handling) or possibly its error path (which will drop through to the .catch() clause).
Not too sure about the signature of the .catch() callback. You would normally expect it to accept a single error argument.
Assign already created deferred.
Try and change this line:
self.deferReturn = $q.defer();
self.deferReturn = d;
Here is my code, it loops through forEach and prints out '1' but never returns from object.save() & never prints out 2, 3 or anything else. I have tried a bunch of other ways but none seems to work.
Note: response.succes(or error) is not being called anywhere, the code is definitely waiting for object.save() to be completed.
var promise = new Parse.Promise();
var query = new Parse.Query("SomeClass");
query.find().then(function(results) {
var promises = [];
results.forEach(function(object) {
object.set("SomeColumnName", true);
console.log('1');
promises.push(object.save(null, {
success: function(result) {
alert('2');
return ;
},
error: function(result, error) {
alert('3');
return ;
}
}));
});
Parse.Promise.when(promises).then(function() {
console.log('inside resolve');
promise.resolve();
}, function() {
console.log('inside reject');
promise.reject();
});
});
return promise;
You're on the right track, but you should take advantage of the fact that most of the sdk functions create and return promises for you. With those, you can substantially simplify the code:
// very handy utility library that provides _.each among many other things
// www.underscorejs.org
var _ = require('underscore');
// answer a promise to modify all instances of SomeClass
function changeSomeClass() {
var query = new Parse.Query("SomeClass");
// if there are more than 100 rows, set query.limit up to 1k
return query.find().then(function(results) { // find returns a promise
_.each(results, function(result) {
result.set("SomeColumnName", true);
});
return Parse.Object.saveAll(results); // and saveAll returns a promise
});
}
Wrap it in a cloud function and call success/error like this:
Parse.Cloud.define("changeSomeClass", function(request, response) {
changeSomeClass().then(function(result) {
response.success(result);
}, function(error) {
response.error(error);
});
});
You can only have one Parse request happening at a time for each object. If multiple requests are sent, all but the first are ignored. You're probably trying to do this while other threads are making Parse requests for those objects. I know that if you save an object, it also saves it's child objects, so you could be hitting a problem with that. Make sure you do as much as you can in background threads with completion blocks, or use saveEventually / fetchEventually where possible.
I have a function which issues two async requests before yielding some data.
The caller of the method does not need to know about its implementation details. All the caller needs is:
The data returned from the second request.
The ability to call abort and not be returned data.
This is complicated by the fact that abort can be called after the first promise is done. The second request is already in-flight, but the caller has yet to receive data. So, the caller assumes it can call abort, but rejecting the first promise will have no effect.
I work around this issue with the following, but it feels pretty hacky. Am I missing something?
var ajaxOptions = {
url: 'https://www.googleapis.com/youtube/v3/search',
data: {
part: 'id',
key: 'AIzaSyDBCJuq0aey3bL3K6C0l4mKzT_y8zy9Msw',
q: 'Hello'
}
};
function search(options) {
var jqXHR = $.ajax(ajaxOptions);
var innerJqXHR = null;
jqXHR.then(function(data, statusText, jqXHR) {
innerJqXHR = $.ajax(ajaxOptions);
innerJqXHR.done(options.done);
innerJqXHR.fail(options.fail);
return innerJqXHR;
}, options.fail);
return {
promise: jqXHR,
innerPromise: innerJqXHR,
fullAbort: function() {
jqXHR.abort();
if (innerJqXHR !== null) {
innerJqXHR.abort();
}
}
}
}
var searchReturn = search({
done: function() {
console.log('inner done');
},
fail: function() {
console.log('inner or outer fail');
}
});
searchReturn.fullAbort();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Since your code looks a little like pseudo-code (unanswered questions in it for me), the best I can offer is a framework of an idea. The general idea is that you return two things from your search function, a promise that is resolved only when both ajax calls are resolved and an abort function that can be called to abort the process.
function search(options) {
var activeAjax = $.ajax(args for first ajax call).then(function(data) {
// second ajax call changes activeAjax so abort will work
// on the active ajax call
activeAjax = $.ajax(args for second ajax call).then(function(data) {
activeAjax = null;
// collect your final data here and return it
// this will be the resolved value of the final promise
return finalData;
});
return activeAjax;
});
return {
promise: activeAjax,
abort: function() {
if (activeAjax) {
activeAjax.abort();
}
}
};
}
// usage:
var searchInProgress = search(...);
searchInProgress.promise.then(function(data) {
// search finished successfully, answer is in data
}, function(err) {
// either one of the promises failed
});
// and, at any time, you can call searchInProgress.abort();
I'm looking to execute a callback upon the full completion of a recursive function that can go on for an undetermined amount of time. I'm struggling with async issues and was hoping to get some help here. The code, using the request module, is as follows:
var start = function(callback) {
request.get({
url: 'aaa.com'
}, function (error, response, body) {
var startingPlace = JSON.parse(body).id;
recurse(startingPlace, callback);
});
};
var recurse = function(startingPlace, callback) {
request.get({
url: 'bbb'
}, function(error, response, body) {
// store body somewhere outside these funtions
// make second request
request.get({
url: 'ccc'
}, function(error, response, body) {
var anArray = JSON.parse(body).stuff;
if (anArray) {
anArray.forEach(function(thing) {
request.get({
url: 'ddd'
}, function(error, response, body) {
var nextPlace = JSON.parse(body).place;
recurse(nextPlace);
});
})
}
});
});
callback();
};
start(function() {
// calls final function to print out results from storage that gets updated each recursive call
finalFunction();
});
It seems that once my code goes past the for loop in the nested requests, it continues out of the request and ends the initial function call while the recursive calls are still going on. I want it to not finish the highest-level iteration until all the nested recursive calls have completed (which I have no way of knowing how many there are).
Any help is GREATLY appreciated!
In your example you have no recursive calls. If I understand correctly you want to say that recurse(point, otherFunc); is the beginning of a recursive call.
Then just go back to the definition of the recursive call (which you have not shown in your post) and do this (add a third argument for a callback function to be called in the end of recursion; the caller will pass it as a parameter):
function recurse(startingPlace, otherFunc, callback_one) {
// code you may have ...
if (your_terminating_criterion === true) {
return callback_one(val); // where val is potentially some value you want to return (or a json object with results)
}
// more code you may have
}
Then in the original code that you posted, make this call instead (in the inner-most part):
recurse(startingPlace, otherFunc, function (results) {
// results is now a variable with the data returned at the end of recursion
console.log ("Recursion finished with results " + results);
callback(); // the callback that you wanted to call right from the beginning
});
Just spend some time and try to understand my explanation. When you understand, then you will know node. This is the node philosophy in one post. I hope it is clear. Your very first example should look like this:
var start = function(callback) {
request.get({
url: 'aaa.com'
}, function (error, response, body) {
var startingPlace = JSON.parse(body).id;
recurse(startingPlace, otherFunc, function (results) {
console.log ("Recursion finished with results " + results);
callback();
});
});
};
Below is only additional information in case you are interested. Otherwise you are set with the above.
Typically in node.js though, people return an error value as well, so that the caller knows if the function that was called has finished successfully. There is no big mystery here. Instead of returning just results people make a call of the form
return callback_one(null, val);
Then in the other function you can have:
recurse(startingPlace, otherFunc, function (recError, results) {
if (recErr) {
// treat the error from recursion
return callback(); // important: use return, otherwise you will keep on executing whatever is there after the if part when the callback ends ;)
}
// No problems/errors
console.log ("Recursion finished with results " + results);
callback(); // writing down `return callback();` is not a bad habit when you want to stop execution there and actually call the callback()
});
Update with my suggestion
This is my suggestion for the recursive function, but before that, it looks like you need to define your own get:
function myGet (a, callback) {
request.get(a, function (error, response, body) {
var nextPlace = JSON.parse(body).place;
return callback(null, nextPlace); // null for no errors, and return the nextPlace to async
});
}
var recurse = function(startingPlace, callback2) {
request.get({
url: 'bbb'
}, function(error1, response1, body1) {
// store body somewhere outside these funtions
// make second request
request.get({
url: 'ccc'
}, function(error2, response2, body2) {
var anArray = JSON.parse(body2).stuff;
if (anArray) {
// The function that you want to call for each element of the array is `get`.
// So, prepare these calls, but you also need to pass different arguments
// and this is where `bind` comes into the picture and the link that I gave earlier.
var theParallelCalls = [];
for (var i = 0; i < anArray.length; i++) {
theParallelCalls.push(myGet.bind(null, {url: 'ddd'})); // Here, during the execution, parallel will pass its own callback as third argument of `myGet`; this is why we have callback and callback2 in the code
}
// Now perform the parallel calls:
async.parallel(theParallelCalls, function (error3, results) {
// All the parallel calls have returned
for (var i = 0; i < results.length; i++) {
var nextPlace = results[i];
recurse(nextPlace, callback2);
}
});
} else {
return callback2(null);
}
});
});
};
Note that I assume that the get request for 'bbb' is always followed by a get request for 'ccc'. In other words, you have not hidden a return point for the recursive calls where you have the comments.
Typically when you write a recursive function it will do something and then either call itself or return.
You need to define callback in the scope of the recursive function (i.e. recurse instead of start), and you need to call it at the point where you would normally return.
So, a hypothetical example would look something like:
get_all_pages(callback, page) {
page = page || 1;
request.get({
url: "http://example.com/getPage.php",
data: { page_number: 1 },
success: function (data) {
if (data.is_last_page) {
// We are at the end so we call the callback
callback(page);
} else {
// We are not at the end so we recurse
get_all_pages(callback, page + 1);
}
}
}
}
function show_page_count(data) {
alert(data);
}
get_all_pages(show_page_count);
I think you might find caolan/async useful. Look especially into async.waterfall. It will allow you to pass results from a callback from another and when done, do something with the results.
Example:
async.waterfall([
function(cb) {
request.get({
url: 'aaa.com'
}, function(err, res, body) {
if(err) {
return cb(err);
}
cb(null, JSON.parse(body).id);
});
},
function(id, cb) {
// do that otherFunc now
// ...
cb(); // remember to pass result here
}
], function (err, result) {
// do something with possible error and result now
});
If your recursive function is synchronous, just call the callback on the next line:
var start = function(callback) {
request.get({
url: 'aaa.com'
}, function (error, response, body) {
var startingPlace = JSON.parse(body).id;
recurse(startingPlace, otherFunc);
// Call output function AFTER recursion has completed
callback();
});
};
Else you need to keep a reference to the callback in your recursive function.
Pass the callback as an argument to the function and call it whenever it is finished.
var start = function(callback) {
request.get({
url: 'aaa.com'
}, function (error, response, body) {
var startingPlace = JSON.parse(body).id;
recurse(startingPlace, otherFunc, callback);
});
};
Build your code from this example:
var udpate = function (callback){
//Do stuff
callback(null);
}
function doUpdate() {
update(updateDone)
}
function updateDone(err) {
if (err)
throw err;
else
doUpdate()
}
doUpdate();
With ES6, 'es6-deferred' & 'q'. You could try as following,
var Q = require('q');
var Deferred = require('es6-deferred');
const process = (id) => {
var request = new Deferred();
const ids =//do something and get the data;
const subPromises = ids.map(id => process(id));
Q.all(subPromises).then(function () {
request.resolve();
})
.catch(error => {
console.log(error);
});
return request.promise
}
process("testId").then(() => {
console.log("done");
});
I need to set an async callback, because a function fetches content from a remote location. I'm doing this:
$.when( priv[box.view.renderWith](content, box.view.gadget_id) ).then(function(late) {
console.log("done");
console.log(late)
console.log($(content))
$(content).append(late).enhanceWithin();
});
with my when function triggering a single Ajax request. In it's callback I'm returning an element to append to $(content).
My problem is, the then function fires immediately and long before my ajax callback is run and returns something.
Question:
Is it not possible to use when() with a function that makes an ajax-request? Do I have to make the ajax request directly in when()? Or why is then() triggered right away? How could I workaround this?
Thanks!
EDIT:
My current version of the snippet:
$.when( priv[box.view.renderWith](content, box.view.gadget_id) ).then(function(fragment) {
// DOM manip...
console.log("NOW WE ARE DONE WITH WHEN");
console.log(fragment)
$(content).append(fragment).enhanceWithin();
});
And the function, I'm calling (without content generation part):
priv.constructListbox = function (element, internal) {
var no_data_body,
no_data_cell,
portable,
gadget_id = element.getAttribute("data-gadget-id") || internal,
settings = priv.gadget_properties[gadget_id],
portal_type = settings.portal_type_title,
// wrapper
$parent = $(element.parentNode);
if (settings !== undefined) {
// ASYNC > this will trigger an Ajax request
portable = priv.erp5.allDocs({
"query": "type: \"" + settings.datasource + "\"",
"limit": [0, (settings.configuration.pagination.items_per_page_select[0] || 30)],
"wildcard_character": "%",
"include_docs": true
}).always(function (answer) {
.... stuff ...
// finish
// return to calling function
if (internal) {
console.log("foo");
console.log("no we only give back a fragment");
return fragment_container;
}
$parent.empty().append( fragment_container ).enhanceWithin();
});
// if internal call, return the promise object
if (internal) {
console.log("foo internal, promise");
return portable;
}
} else {
// error handler
}
};
When I console portable inside my then callback, I get the promise object, so now the function is returning the promise vs an element. However when resolved, I was hoping I would get my fragment_container when I'm not ... getting anything :-(
Hopefully clear enough.
Best advice I ever heard is to treat Async programming like normal functions and then add the promises at the end.
I'm having diffculty seeing where you are setting fragment_container, but here goes..
priv.constructListbox = function (element, internal) {
var dfd = new $.Deferred();
...
if (settings !== undefined) {
portable = priv.erp5.allDocs({
"query": "type: \"" + settings.datasource + "\"",
"limit": [0, (settings.configuration.pagination.items_per_page_select[0] || 30)],
"wildcard_character": "%",
"include_docs": true
}).always(function (answer) {
.... stuff ...
// finish
// return to calling function
if (internal) {
console.log("foo");
console.log("no we only give back a fragment");
dfd.resolve({message:"You did it!", element: fragment_container });
}
$parent.empty().append( fragment_container ).enhanceWithin();
});
} else {
dfd.reject({result:"Nope - no data came out"});
// error handler
}
return dfd.promise();
};
then it's easy to see what you've returned:
$.when( priv[box.view.renderWith](content, box.view.gadget_id) ).then(function(fragment) {
console.log("NOW WE ARE DONE WITH WHEN");
console.log(fragment);
},
function(fragment) {
console.log("It failed");
console.log(fragment);
});