node.js Q promises does not propagate exception through rejection handler - javascript

I am using requestify to make some HTTP requests. The module internally makes use other modules such as HTTP, socket etc. It also makes use of Q promises.
The modules used by requestify can asynchronously throw exceptions (e.g. ECONNREFUSED can be thrown by the socket module). Whenever an exception is thrown, the control flow breaks away to my process's generic uncaught-exception handler and does not get propagated to the promise's error/reject handler.
the code below is a sample code, which represents the issue.
Question - Is it possible for for me to handle such exceptions through the Q promise's rejection handler?
// sample code
var _inspect = require('util').inspect;
var _q = require('q');
function handle_uncaught_exception(ex) {
console.log(" Uncaught Exception: " + _inspect(ex));
process.exit(2);
}
process.on('uncaughtException', handle_uncaught_exception);
function foo_3rd_party_code() {
var d = _q.defer();
setTimeout(
function () {
throw new Error("TEST_EXCEPTION");
},
1000
);
return d.promise;
}
foo_3rd_party_code()
.then(
function (val) {
console.log("Promise Fullfilled: " + _inspect(val));
process.exit(0);
},
function (err) {
// it would be nice to see the TEST_EXCEPTION here.
console.log("Promise Rejected: " + _inspect(err));
process.exit(1);
}
);

foo_3rd_party_code can't catch or handle the exception because it can't see it. You must use the reject method of the promise for that.
function foo_3rd_party_code() {
var d = _q.defer();
setTimeout(
function () {
d.reject(new Error("TEST_EXCEPTION"));
},
1000
);
return d.promise;
}

Related

How to cancel a promise with $q in angular js

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;

Error: "ReferenceError: promise is not defined" adding promise to my code

I have 3 web service calls being made from Node.js. 2 are ReST, 1 is SOAP. All are wrapped in Promises.
I've gotten my ReST requests to return the promises correctly and these are accessible in the Promise.all block but when I add my SOAP request, I get a message saying Promise is not defined.
I'm using node v8.2.1. I've tried request and request-promise but the same thing happens. My code looks like this - anything I'm obviously doing wrong?
const locationRequest = require('request');
var soapPromise = new Promise(function(resolve, reject) {
locationRequest(options1, function(error, response, output) {
if (error) {
console.info("soap error: " + error);
reject(error);
}
else {
console.info("soap success: " + response);
resolve(response);
}
});
return promise;
});
Promise.all([restPromise, photoPromise, soapPromise]) //addition of soapPromise causes the issue
.then(function([restResult, photoResult, soapResult]) {
//respond to client
console.info("Resource: " + restResult.name);
console.info("Photo Path: " + photoResult);
console.info("Soap: " + soapResult);
})
.catch(function(error) {
console.info("promise all error: " + error);
res.send('done');
//catch an error generated from either request
})
Adding the soapPromise stuff gives me:
ReferenceError: promise is not defined
Remove the return promise; line. You're not expected to return anything out of the Promise executor (the callback you give new Promise), and it doesn't create a promise variable. So promise is an undefined identifier at that point, hence the ReferenceError.

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).

How to assign multiple onError functions to a promise (returned by angular's $http.post)

My AngularJS code needs to chain multiple onSuccess, onError functions to a promise returned by $http.post
var promise = $http.post(url);
promise
.then(
/*success 1*/function () { console.log("success 1"); },
/*error 1*/function () { console.log("error 1"); })
.then(
/*success 2*/function () { console.log("success 2"); },
/*error 2*/function () { console.log("error 2"); });
The problem with above code is that it prints error 1 > success 2 when the HTTP response fails instead of error 1 > error 2.
I did some research on stackoverflow and found that when you have access to $q you can just do $q.reject() in error 1 to trigger error 2 but in my case i only have access to the promise returned by $http.post. So what do I do?
P.S. Of course, I can call error2() from inside of error 1 but i want to chain them because it looks more readable and extensible.
Any ideas?
Returning a value (or returning no value) from a success/error handler will resolve the promise for the next then block in the chain. To propagate the rejection, return $q.reject():
var promise = $http.post(url);
promise
.then(
/*success 1*/function () { console.log("success 1"); },
/*error 1*/function () { console.log("error 1"); return $q.reject();})
.then(
/*success 2*/function () { console.log("success 2"); },
/*error 2*/function () { console.log("error 2"); });
Your question stems from some misunderstanding of what promises enable - namely, async code composition that parallels that of a synchronous code with try/catch, with proper exception handling.
I am specifically referring to your statement:
"but i want to chain them because it looks more readable and extensible."
as the source of misunderstanding of chaining.
If your example was synchronous (assuming all async calls were blocking), this is likely what you would have wanted to do:
try {
var data = $http.post(url); // blocking
var res1 = doSuccess1(data);
var ret = doSuccess2(res1);
}
catch(e){
errorHandler1(e);
errorHandler2(e);
}
And not this:
try {
try {
var data = $http.post(url);
var res1 = doSuccess1(data);
} catch (e) {
errorHandler1(e);
// throw ""; // this is what returning $q.reject would have done - a rethrow
}
} catch (e) {
errorHandler2(e);
}
var ret = doSuccess2(res1);
which is what you would have achieved with your chaining. In other words, nested try/catch and unhandled exception in doSuccess2.
The following is the async parallel of the first approach:
var ret;
$http.post(url)
.then(function(data){
var res1 = doSuccess1(data);
ret = doSuccess2(res1);
}
.catch(function(e){ // or .then(null, handler)
doError1(e);
doError2(e);
})
And if one of doSuccessN functions were also async:
var ret;
$http.post(url)
.then(doSuccess1Async)
.then(function(res1){
ret = doSuccess2(res1);
}
.catch(function(e){ // or .then(null, handler)
doError1(e);
doError2(e);
})
Just wrap the handlers in a function, in the success / error handler parameters:
var promise = $http.post(url);
promise
.then(function(argX, argY){
success1(argX, argY);
success2(argX, argY);
},
function(argX, argY){
error1(argX, argY);
error2(argX, argY);
});

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;
}

Categories

Resources