How to cancel a promise with $q in angular js - javascript

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;

Related

Looping with Q promises in node.js

I'm very new to js and node coming from a java world. I threw a test program together based on the real program I'm writing. It's using the Q library in a node.js program.
The for loop is only acting on the last item in the array.. it's like all the other Q.promises are lost.
My output is:
registerELB
de-registerELB
resumeASG 4
started instance 4
resumeASG 4
started instance 4
resumeASG 4
started instance 4
resumeASG 4
started instance 4
autoshutdown complete.
Where as I expect it to be:
resumeASG 1
started instance 1
resumeASG 2
started instance 2
resumeASG 3
started instance 3
resumeASG 4
started instance 4
registerELB
de-registerELB
autoshutdown complete.
what's going on in the loop? Are the promises getting lost?
why is registerELB and de-registerELB happening before my first .then()
var Q = require('q');
var startedInstances = [];
test();
function test() {
getInstances()
.then(function(data) {
var promise = Q.defer();
for(var x = 0; x < data.length; x++) {
var somedata = data[x];
console.log(somedata);
startOrStop(somedata)
.then(function (updateStatus) {
var promiseinner = Q.defer();
console.log(somedata);
if(updateStatus == 'start') {
return Q.nfcall(resumeASG, somedata)
.then(Q.nfcall(startInstance, somedata));
} else if(updateStatus == 'stop') {
return Q.nfcall(suspendASG, somedata)
.then(Q.nfcall(stopInstance, somedata));
}
promiseinner.resolve('complete');
return promiseinner.promise;
})
}
promise.resolve('successful');
return promise.promise;
}, function(error) {
console.log('Failed to get instance data ' + error);
//context.fail();
}).then(function(data) {
return registerELB();
}, function(error) {
console.log('Failed to Re-register ELB ' + error);
//context.fail();
}).then(function(data) {
console.log('autoshutdown complete.')
//context.succeed(data);
}, function(error) {
console.log('autoshutdown failed to complete. ' + error)
//context.fail();
}).done();
};
function getInstances() {
var promise = Q.defer();
startedInstances.push(1);
startedInstances.push(2);
startedInstances.push(3);
startedInstances.push(4);
promise.resolve(startedInstances);
return promise.promise;
}
function suspendASG (asg) {
var promise = Q.defer();
console.log('suspendASG ' + asg);
promise.resolve(asg);
return promise.promise;
}
function resumeASG (asg) {
var promise = Q.defer();
console.log('resumeASG ' + asg);
promise.resolve(asg);
return promise.promise;
}
function registerELB() {
var promise = Q.defer();
console.log('registerELB ');
console.log('de-registerELB ');
promise.resolve('success elb');
return promise.promise;
}
function startInstance(instanceId) {
var promise = Q.defer();
console.log('started instance ' + instanceId);
promise.resolve(instanceId);
return promise.promise;
}
function stopInstance(instanceId) {
var promise = Q.defer();
console.log('stopped instance ' + instanceId);
promise.resolve(instanceId);
return promise.promise;
}
function startOrStop (instance) {
var promise = Q.defer();
promise.resolve('start');
return promise.promise;
}
This is a classic issue with a for loop and any async operation inside the loop. Because your async operations inside the for loop complete some time LATER after the for loop has already finished, any variables set inside your for loop will have the values at the end of the loop when any of your async operations completes.
In your particular case, the somedata variable is a problem here. You can generally work-around this issue by using a closure so that each invocation of your async operations has it's own function and thus it's own state that uniquely survives until the async operation has completed.
It looks to me like you may also have other synchronization issues going on here since your for loop is running all your operations in parallel and you have no link between your inner async operations and the outer promise you are resolving, so I suspect there are other problems here too.
But, if you just change your for loop to a .forEach() loop, then each invocation of the loop will have it's own unique state. I honestly can't say that there aren't other issues in this code (since I have no way of testing it), but this should at least solve one issue related to the for loop:
test();
function test() {
getInstances()
.then(function(data) {
var promise = Q.defer();
// -------- Change made here -------------
// change for loop to .forEach() loop here
// to create closure so your async operations
// each can access their own data
data.forEach(function(somedata) {
console.log(somedata);
startOrStop(somedata)
.then(function (updateStatus) {
var promiseinner = Q.defer();
console.log(somedata);
if(updateStatus == 'start') {
return Q.nfcall(resumeASG, somedata)
.then(Q.nfcall(startInstance, somedata));
} else if(updateStatus == 'stop') {
return Q.nfcall(suspendASG, somedata)
.then(Q.nfcall(stopInstance, somedata));
}
promiseinner.resolve('complete');
return promiseinner.promise;
})
});
promise.resolve('successful');
return promise.promise;
}, function(error) {
console.log('Failed to get instance data ' + error);
//context.fail();
}).then(function(data) {
return registerELB();
}, function(error) {
console.log('Failed to Re-register ELB ' + error);
//context.fail();
}).then(function(data) {
console.log('autoshutdown complete.')
//context.succeed(data);
}, function(error) {
console.log('autoshutdown failed to complete. ' + error)
//context.fail();
}).done();
};
Also, do you realize that your inner promises (inside the .forEach() loop) are not linked at all to the outer promise? So, getInstances() will resolve its promise, long before the inner promises in the .forEach() loop are done? This looks broken to me. I don't know exactly what to recommend because I don't know which operations you want to run in parallel and which should be in series. You may need to do a Promise.all() that collects all the promises from the for loop and then use that to resolve your outer promise.

How to find out if WinJS.Promise was cancelled by timeout or cancel() call

I have a server request that is wrapped in a timeout promise.
var pendingRequest = WinJS.Promise.timeout(5000, requestAsync).
The user also has a "Cancel" button on the UI to actively cancel the request by executing pendingRequest.cancel(). However, there is no way to find out that the promise has been cancelled by the user or by the timeout (since timeout calls promise.cancel() internally too).
It would have been nice of WinJS.Promise.timeout would move the promise in the error state with a different Error object like "Timeout" instead of "Canceled".
Any idea how to find out if the request has been cancelled by the timeout?
Update: How about this solution:
(function (P) {
var oldTimeout = P.timeout
P.timeout = function (t, promise) {
var timeoutPromise = oldTimeout(t);
if (promise) {
return new WinJS.Promise(function (c, e, p) {
promise.then(c,e,p);
timeoutPromise.then(function () {
e(new WinJS.ErrorFromName("Timeout", "Timeout reached after " + t + "ms"));
});
});
} else {
return timeoutPromise;
}
};
})(WinJS.Promise);
According to the documentation,
... the promise enters the error state with a value of Error("Canceled")
Thus, error.message === 'Canceled' can be detected in your error handler.
In addition, WinJS.Promise allows an onCancel callback to be specified at construction time.
var promise = new WinJS.Promise(init, onCancel);
where init and onCancel are both functions.
Here's a demo.
Edit
Ah OK, sorry I misread the question. I understand now that you wish to distinguish between a timeout and a manually canceled promise.
Yes, it can be done, by making an appropriate message available to both :
a WinJS promise's onCancel callback
a chained "catch" callback.
First, extend WinJS.Promise.prototype with a .timeout() method :
(function(P) {
P.prototype.timeout = function (t) {
var promise = this;
promise.message = 'Canceled';
P.timeout(t).then(function() {
promise.message = 'Timeout';
promise.cancel();
});
return promise.then(null, function() {
if(error.message == 'Canceled') {
throw new Error(promise.message); //This allows a chained "catch" to see "Canceled" or "Timeout" as its e.message.
} else {
throw error; //This allows a chained "catch" to see a naturally occurring message as its e.message.
}
});
};
})(WinJS.Promise);
This becomes a method of each instance of WinJS.Promise(), therefore does not conflict with the static method WinJS.Promise.timeout() .
Now, use the .timeout() method as follows :
function init() {
//whatever ...
}
function onCancel() {
console.log('onCacnel handler: ' + this.message || `Canceled`);
}
var promise = new WinJS.Promise(init, onCancel);
promise.timeout(3000).then(null, function(error) {
console.log('chained catch handler: ' + error.message);
});
promise.cancel();
/*
* With `promise.cancel()` uncommented, `this.message` and `error.message` will be "Canceled".
* With `promise.cancel()` commented out, `this.message` and `error.message` will be "Timeout".
*/
Demo (with extra code for button animation).

Unhandled rejection in Bluebird

I have the following code. It works fine when f2 throws no error.
If there is an error, it generates an Unhandled rejection Error.
What's the proper way to rewrite the code to avoid Unhandled rejection Error and propagate it correctly to catch in f1?
let Bluebird = require('bluebird'),
mkdirp = Bluebird.promisify(require('mkdirp')),
request = Bluebird.promisify(require('request')),
writeFile = Bluebird.promisify(require('fs').writeFile);
function f1() {
.........
f2(path, fileName, options).then(.....).catch(....);
}
function f2(path, fileName, options) {
p = mkdirp(path).then(request(options).then(res => {
if (res[0].statusCode === 200) {
writeFile(fileName, res[0].body);
return res[0].body;
} else {
throw new Error(res[0].statusCode + ': ' + res[0].body);
}
}));
return p;
}
The problem is that you are passing a promise into .then() in f2. .then() will ignore anything that is not a function, so all that f2 is really returning is a promise for mkdirp(this.path) and that's a big bug for a few reasons. If an error is thrown in request(options)'s then handler, then there will be nothing to handle it.
Also, you are not doing anything to handle a possible error from writeFile. If you call writeFile, you either need to return a promise chain that includes it, or add logic to handle it within f2.
Since it looks like you can run mkdirp() and request() in parallel here, but you are not using the result of mkdirp() I would say this is the way to go:
function f2(path, fileName, options) {
var p = mkdirp(path).return(request(options)).then(res => {
if (res[0].statusCode === 200) {
return writeFile(fileName, res[0].body)
.return(res[0].body);
} else {
throw new Error(res[0].statusCode + ': ' + res[0].body);
}
});
return p;
}

How to pass argument to "then" function

I'm trying to learn using deferred and I'm stumbled as I'm not getting expected arguments in the "then" block.
var makeCall = function (err, param) {
var deferred = Q.defer();
setTimeout(function() {
console.log(1111, err, param);
deferred.resolve(err, param);
}, 1000);
return deferred.promise;
};
makeCall('test', '11').then(function(err, data) {
console.log(222, err, data);
});
Console. with 1111 outputs correct data that was returned from an Ajax call but 222 does not.
http://jsfiddle.net/M2V44/
deferred.resolve can accept only one argument and that is to mark the success of the asynchronous call. To notify of the failure, you need to use deferred.reject. So your code has to be changed like this
var makeCall = function(err,param){
setTimeout(function () {
console.log(1111, err, param);
var deferred = Q.defer();
if (err) {
deferred.reject(err);
} else {
deferred.resolve(param);
}
}, 1000);
return deferred.promise;
};
makeCall(undefined, '11').then(function (data) {
console.log(222, data);
}, function (err) {
console.log(333, err);
});
This will print 222 '11', to simulate the failure case, just invoke makeCall with any Truthy value as the first argument, for example
makeCall('11')....
it will invoke the failure handler, and the output will be 333 '11'.
In your case, I'd avoid the deferred altogether.
var makeCall = function(err,param){
if(err) return Q.reject(err);
return Q(param).delay(1000);
};
(fiddle)
The usage is similar to thefoureye's answer from earlier, since promises are like synchronous code, you interact with them using return values and catch statements. Nodebacks ((err,data)) and callbacks more generally remove many desirable properties from asynchronous code, and promises aim to restore those properties.
makeCall(new Error("Hello"),"SomeValue").then(function(cata){
console.log("Got correct data!",data);
}).catch(function(err){
console.log("Got error :(",err); // this would happen since we passed an error.
});
I also assume that that this function is imaginary, and not representative of a real API.
You mainly use deferred objects when converting an API to promises, which you don't need to in this case.
Note, a) Not certain if interpret Question correctly; b) Promise appear not implemented universally same in every browser, nor at jsfiddle. This may be helpful JavaScript Promises http://www.html5rocks.com/en/tutorials/es6/promises/ (where below piece can be tried at console; should also work at mozilla nightly console, which appear to implement Promise object)
Try this (pattern)
var makeCall = function(err, param) {
return new Promise(function(resolve, reject ) {
setTimeout(function() {
console.log(1111, err, param);
return (err && param) ?
resolve(err, param) :
reject(Error("error"))
})
}, 1000);
};
makeCall("test", "11")
.then(function(result) {
console.log(222, result);
makeCall("test2","11");
makeCall("abc", 123) // `chain` test
},
function(err) {
console.log(err)
});

Dojo using deferred functions to get data in ajax callback function

I have a function with a return however in the function there is an async request which holds the value that is suppose to be returned by the function. I understand with the nature of async request the function will complete and not return a value while waiting on the async function to complete.
I attempted to use dojo deferred functions to have my function PostInformation() to return a value within the ajax request callback. I am having some issues and i am not sure where my issue is. Under is my code:
Dojo Deferred Function
function PostInformation(){
var hasErrors = false;
var containers = [dijit.byId("container1"), dijit.byId("container2")];
var Employee = {
//data
};
var def = new dojo.Deferred();
def = dojo.xhrPost({
url: 'hello',
content: Employee,
load: function (data) {
formErrors = {
"errors": true,
"fName": "123",
"surname": "456",
"oNames": "789",
"bSurname": "784585"
};
//formErrors = (JSON.parse(data)).formErrors;
$.each(formErrors, function (key, value) {
if (key == 'errors') {
hasErrors = value;
//console.log('hasErrors set to '+value);
}
});
if (hasErrors == true) {
for (var i = 0; i < containers.length; i++) {
var processingContainer = containers[i];
dojo.forEach(processingContainer.getChildren(), function (wid) {
var widgetName = wid.attr('id');
$.each(formErrors, function (key, value) {
if (key == widgetName && value.length > 0) {
var myWidget = dijit.byId(widgetName);
//var wdgName = dijit.byId(widgetName).attr("id");
var myWidgetValue = value;
myWidget.validator = function () {
//console.log('Attribute Name is :' + wdgName + ' Error Value is : ' + myWidgetValue);
//console.log(wdgName + " : "+myWidgetValue);
this.set("invalidMessage", myWidgetValue);
};
myWidget._hasBeenBlurred = true;
myWidget.validate();
}
});
});
}
}
console.log(hasErrors);
def.resolve(hasErrors);
},
error: function(err){
console.log(err);
def.reject(err);
}
});
def.then(function(data){
console.log('In the then function');
//alert('In the def.then and the results is : ' + data);
if(data == true){
return false;
}else{return true;}
},function(err){
return false;
alert('In the def.error and there has been an error ' + err);
});
//return the value of hasErrors here
};
Devdar, you are making heavy wether out of something quite simple. In particular, you don't need to loop through an object to access one of its properties, and the variable hasErrors is not really necessary.
Your code should simplify to something like this :
function PostInformation() {
var $containers = $("#container1, #container2");
var Employee = {
//data
};
return dojo.xhrPost({
url: 'hello',
content: Employee
}).then(function(data) {
data = JSON.parse(data);
var formErrors = data.formErrors;
if(formErrors.errors) {
$containers.each(function(i, c) {
$(c).children().each(function(wid) {
var val = formErrors[wid.id],
myWidget;
if(val) {
myWidget = dijit.byId(wid.id);
myWidget.validator = function() {
this.set("invalidMessage", val);
};
myWidget._hasBeenBlurred = true;
myWidget.validate();
}
});
});
//Send an enhanced error object down the "error" route
throw $.extend(formErrors, {
'message': 'PostInformation(): validation failure'
});
}
//Send the data object down the "success" route
return data;
});
};
PostInformation().then(function(data) {
console.log('PostInformation(): everything went OK');
//access/process `data` here if necessary
//and/or just display a nice "success" message to the user
}, function(err) {
console.error(err.message);
});
Barring mistakes on my part, this code should do everything you want and more. As with your own code, it processes the server's JSON response and returns a Promise, but that's where the similarity stops.
In your code, you seek to return a Promise which is eventually resolved with a boolean to indicate whether or not errors were detected. Whilst this will (if correctly written) meet your immediate needs, it is not the best Promise logic.
In my code, the Promise is resolved only if validation succeeds and rejected if validation fails for whatever reason. Not only is this logically correct behaviour for a Promise (success goes down the success route, and errors go down the error route) but as a bonus should (see note below) also allow you to pass more information to whetever function(s) eventually handle errors. I choose to pass the whole formErrors object enhanced with an error message, thus providing a great deal of freedom in the error handler to display/log/etc as much or as little as is appropriate, and with virtually no assumption inside PostInformation() as to what will happen subsequently. You currently believe that you will only read and act on the boolean formErrors.errors but it could be beneficial to pass as much error data as possible thus allowing yourself the freedom to change your mind at a later date without needing to change anything in PostInformation().
In this regard you can think of PostInformation() as an agent of the server-side service; and like that service, it can be written with incomplete knowledge (or maybe no knowledge at all) of how the (promise of) data/errors it delivers will be used by "consumer code".
NOTE: I have to admit that I'm not 100% familiar with Dojo's Promises, so I'm not sure that a JS plain object can be thrown in the way I indicate. I have found evidence but not proof that it can. For that reason, I am cautious above in saying "your code should simplify to something like this" Anyway, that issue aside, the principle of sending success down the success route and errors down the error route should still apply.
I'd suggest this where you create your own Deferred() object, return it from your PostInformation() function and then register .then() handlers on it so you can pick up the resolve or reject on your own Deferred object that happens inside the PostInformation() function.
The way you had it you were creating your own Deferred() object, but then immediately overwriting it with the xhrPost return result which meant def is now something else and you weren't returning your Deferred from PostInformation() so it can be used outside that function to track the progress.
function PostInformation() {
var hasErrors = false;
var containers = [dijit.byId("container1"), dijit.byId("container2")];
var Employee = {
//data
};
var def = new dojo.Deferred();
dojo.xhrPost({
url: 'hello',
content: Employee,
load: function (data) {
formErrors = {
"errors": true,
"fName": "123",
"surname": "456",
"oNames": "789",
"bSurname": "784585"
};
//formErrors = (JSON.parse(data)).formErrors;
$.each(formErrors, function (key, value) {
if (key == 'errors') {
hasErrors = value;
//console.log('hasErrors set to '+value);
}
});
if (hasErrors == true) {
for (var i = 0; i < containers.length; i++) {
var processingContainer = containers[i];
dojo.forEach(processingContainer.getChildren(), function (wid) {
var widgetName = wid.attr('id');
$.each(formErrors, function (key, value) {
if (key == widgetName && value.length > 0) {
var myWidget = dijit.byId(widgetName);
//var wdgName = dijit.byId(widgetName).attr("id");
var myWidgetValue = value;
myWidget.validator = function () {
//console.log('Attribute Name is :' + wdgName + ' Error Value is : ' + myWidgetValue);
//console.log(wdgName + " : "+myWidgetValue);
this.set("invalidMessage", myWidgetValue);
};
myWidget._hasBeenBlurred = true;
myWidget.validate();
}
});
});
}
}
console.log(hasErrors);
def.resolve(hasErrors);
},
error: function (err) {
console.log(err);
def.reject(err);
}
});
return def.promise;
};
PostInformation().then(function (data) {
console.log('In the then function');
// process data value here which will contain the value you resolved with
}, function(err)
// process an error in the ajax result here
});
I think this is more of an issue with design of the function then.
Since the xHR call is asynchronous, the postInformation shouldn't really return anything unless it's the Deferred object itself. An alternative option is to have postInformation do some sort of event publishing (dojo/topic), that other functions will subscribe to and know how to handle said events.

Categories

Resources