Q promise chain exists promise chain after error - javascript

I have a node.js script that opens up a Azure container, takes screenshots of a page across multiple different countries while streaming them to the Azure container. The issue I'm having is if I encounter an error in the streaming process, it finishes the remaining screenshots for that given id and then exits out of the promise chain.
So if I encounter an error at Id 211006, it completes taking all the screenshots, and then exits the stream. It doesn't continue on.
I'm very new to how promises work and how they catch errors, but my understanding is that if, say, 211006 does encounter an error, the script would complete the promise chain, and then show me any error prior to running .fin - that's not the case.
Can anybody help?
AzureService.createContainer()
.then(function () {
return ScreenshotService.getAllCountriesOfId('308572');
})
.then(function () {
return ScreenshotService.getAllCountriesOfId('211006');
})
.then(function () {
return ScreenshotService.getAllCountriesOfId('131408');
})
.then(function () {
return ScreenshotService.getAllCountriesOfId('131409');
})
.then(function () {
return ScreenshotService.getAllCountriesOfId('789927');
})
.then(function () {
return ScreenshotService.getAllCountriesOfId('211007');
})
.then(function () {
return ScreenshotService.getAllCountriesOfId('833116');
})
// Upload Log file into Azure storage
.fin(function () {
AzureService.init({
container: config.azure.storage.msft.CONTAINER.LOG,
account: config.azure.storage.msft.ACCOUNT,
key: config.azure.storage.msft.ACCESS_KEY,
file: config.file.log,
isLogFile: true
});
log.info('Utility: Uploading log file [ %s ] to Azure storage container [ %s ]', AzureService.file, AzureService.container);
return AzureService.uploadLocalFileToStorage()
.then(function () {
return util.deleteFile({fileName: AzureService.file, isLogFile: true});
})
.fail(function (err) {
log.info(err);
})
.done();
})
.fail(function (err) {
log.info(err);
})
.done();

A chain of promises is stopped anytime an error is allowed back into the chain. That sets the promise state to rejected and will call the next error handler in any subsequent .then() handlers, not the fulfilled handler.
If you want the chain to continue, then you need to catch the error. Catching the error will cause the promise infrastructure to consider it "handled" and the promise state will again be fulfilled and it will continue executing fulfilled handlers.
Promise errors are analogous to exceptions. If they are not handled, they will abort processing up until the first exception handler. If they are handled with an exception handler, then processing will continue normally after that exception handler.
In your specific case, if you want the chaing to continue, you will need to handle errors in each of these types of lines:
return ScreenshotService.getAllCountriesOfId('308572');
You can do that like this:
return ScreenshotService.getAllCountriesOfId('308572').then(null, function(err) {
console.log(err);
// error is now handled and processing will continue
});
Since you have a lot of repeated code, you should probably change your code into something that iterates through an array of country IDs rather than just copy lines of code over and over.
Here's a means of using .reduce() to chain all the promises in a loop and get rid of so much repetitive code and handle individual country errors so the chain continues:
var countryIds = ['308572', '211006', '131408', '131409', '789927', '211007', '833116'];
countryIds.reduce(function(p, item) {
return p.then(function() {
return ScreenshotService.getAllCountriesOfId(item).then(null, function(err) {
console.log(err);
});
});
}, AzureService.createContainer())
// Upload Log file into Azure storage
.fin(function () {
... rest of your code continued here

Related

Asserting on catch block code inside forEach loop

I am hard time writing test to assert something happened inside catch block which is executed inside forEach loop.
Prod code
function doSomething(givenResourceMap) {
givenResourceMap.forEach(async (resourceUrl) => {
try {
await axios.delete(resourceUrl);
} catch (error) {
logger.error(`Error on deleting resource ${resourceUrl}`);
logger.error(error);
throw error;
}
});
I am wanting to assert logger.error is being called twice and called with right arguments each time. So I wrote some test like this
describe('Do Something', () => {
it('should log message if fail to delete the resource', function() {
const resource1Url = chance.url();
const givenResourceMap = new Map();
const thrownError = new Error('Failed to delete');
givenResourceMap.set(resource1Url);
sinon.stub(logger, 'error');
sinon.stub(axios, 'delete').withArgs(resource1Url).rejects(thrownError);
await doSomething(givenResourceMap);
expect(logger.error).to.have.callCount(2);
expect(logger.error.getCall(0).args[0]).to.equal(`Error deleting resource ${resource1Url}`);
expect(logger.error.getCall(1).args[0]).to.equal(thrownError);
// Also need to know how to assert about `throw error;` line
});
});
I am using Mocha, sinon-chai, expect tests. Above test is failing saying logger.error is being 0 times.
Thanks.
The problem is that you are using await on a function that doesn't return a Promise. Note that doSomething is not async and does not return a Promise object.
The forEach function is async but that means they'll return right away with an unresolved Promise and you don't ever await on them.
In reality, doSomething will return before the work inside of the forEach is complete, which is probably not what you intended. To do that you could use a regular for-loop like this:
async function doSomething(givenResourceMap) {
for (const resourceUrl of givenResourceMap) {
try {
await axios.delete(resourceUrl);
} catch (error) {
logger.error(`Error on deleting resource ${resourceUrl}`);
logger.error(error);
throw error;
}
}
}
Note that it changes the return type of doSomething to be a Promise object rather than just returning undefined as it originally did. But it does let you do an await on it as you want to in the test (and presumably in production code also).
However since you re-throw the exception caught in the loop, your test will exit abnormally. The test code would have to also change to catch the expected error:
it('should log message if fail to delete the resource', function(done) {
// ... the setup stuff you had before...
await doSomething(givenResourceMap).catch(err => {
expect(logger.error).to.have.callCount(2);
expect(logger.error.getCall(0).args[0]).to.equal(`Error deleting resource ${resource1Url}`);
expect(logger.error.getCall(1).args[0]).to.equal(thrownError);
done();
});
});

How do I handle an error and then immediately break out of a promise chain?

So I have an Express app that uses middleware to parse JSON POST requests and then populate a req.body object. Then I have a promise chain that validates the data against a schema using Joi, and then stores it in a database.
What I would like to do is check if an error was thrown after one of these processes, handle it appropriately by sending a status code, then COMPLETELY ABORT the promise chain. I feel like there should be some EXTREMELY CLEAN AND SIMPLE way to do this, (perhaps some sort of break statement?) but I can't find it anywhere. Here is my code. I left comments showing where I hope to abort the promise chain.
const joi = require("joi");
const createUserSchema = joi.object().keys({
username: joi.string().alphanum().min(4).max(30).required(),
password: joi.string().alphanum().min(2).max(30).required(),
});
//Here begins my promise chain
app.post("/createUser", (req, res) => {
//validate javascript object against the createUserSchema before storing in database
createUserSchema.validate(req.body)
.catch(validationError => {
res.sendStatus(400);
//CLEANLY ABORT the promise chain here
})
.then(validatedUser => {
//accepts a hash of inputs and stores it in a database
return createUser({
username: validatedUser.username,
password: validatedUser.password
})
.catch(error => {
res.sendStatus(500);
//CLEANLY ABORT the promise chain here
})
//Only now, if both promises are resolved do I send status 200
.then(() => {
res.sendStatus(200);
}
)
});
You can't abort a promise chain in the middle. It's going to either call a .then() or a .catch() later in the chain (assuming there are both and assuming your promises resolve or reject).
Usually, the way you handle this is you put one .catch() at the end of the chain and it examines the type of error and takes appropriate action. You don't handle the error earlier in the chain. You let the last .catch() handle things.
Here's what I would suggest:
// helper function
function err(status, msg) {
let obj = new Error(msg);
obj.status = status;
return obj;
}
//Here begins my promise chain
app.post("/createUser", (req, res) => {
//validate javascript object against the createUserSchema before storing in database
createUserSchema.validate(req.body).catch(validationError => {
throw err("validateError", 400)
}).then(validatedUser => {
//accepts a hash of inputs and stores it in a database
return createUser({
username: validatedUser.username,
password: validatedUser.password
}).catch(err => {
throw err("createUserError", 500);
});
}).then(() => {
// success
res.sendStatus(200);
}).catch(error => {
console.log(error);
if (error && error.status) {
res.sendStatus(error.status);
} else {
// no specific error status specified
res.sendStatus(500);
}
});
});
This has several advantages:
Any error propagates to the last .catch() at the end of the chain where it is logged and an appropriate status is sent in just one place in the code.
Success is handled in just one place where that status is sent.
This is infinitely extensible to more links in the chain. If you have more operations that can have errors, they can "abort" the rest of the chain (except the last .catch() by just rejecting with an appropriate error object).
This is somewhat analogous to the design practice of not having lots of return value statements all over your function, but rather accumulating the result and then returning it at the end which some people consider a good practice for a complicated function.
When debugging you can set breakpoints in one .then() and one .catch() to see the final resolution of the promise chain since the whole chain goes through either the last .then() or the last .catch().
.catch returns a resolved Promise by default. You want a rejected Promsise. So, you should return a rejected promise from inside the .catch, so that future .thens won't execute:
.catch(validationError => {
res.sendStatus(400);
return Promise.reject();
})
But note that this will result in a console warning:
Uncaught (in promise) ...
So it would be nice to add another .catch to the end, to suppress the error (as well as catch any other errors that come along):
const resolveAfterMs = ms => new Promise(res => setTimeout(() => {
console.log('resolving');
res();
}), ms);
console.log('start');
resolveAfterMs(500)
.then(() => {
console.log('throwing');
throw new Error();
})
.catch(() => {
console.log('handling error');
return Promise.reject();
})
.then(() => {
console.log('This .then should never execute');
})
.catch(() => void 0);
If you want to avoid all future .thens and future .catches, I suppose you could return a Promise that never resolves, though that doesn't really sound like a sign of a well-designed codebase:
const resolveAfterMs = ms => new Promise(res => setTimeout(() => {
console.log('resolving');
res();
}), ms);
console.log('start');
resolveAfterMs(500)
.then(() => {
console.log('throwing');
throw new Error();
})
.catch(() => {
console.log('handling error');
return new Promise(() => void 0);
})
.then(() => {
console.log('This .then should never execute');
})
.catch(() => {
console.log('final catch');
});
A cleaner solution for what you are trying to accomplish might be to use express-validation, which is a simple wrapper around joi that provides you with express middleware for validation of the body, params, query, headers and cookies of an express request based on your Joi schema.
That way, you could simply handle any Joi validation errors thrown by the middleware within your "generic" express error handler, with something like:
const ev = require('express-validation');
app.use(function (err, req, res, next) {
// specific for validation errors
if (err instanceof ev.ValidationError)
return res.status(err.status).json(err);
...
...
...
}
If you don't want to use the express-validation package, you could write your own simple middleware that does more or less the same thing, as described here (see example here).
One strategy is to separate your error handling in subpromises which have their individual error handling. If you throw an error from them, you'll bypass the main promise chain.
Something like:
return Promise.resolve().then(() => {
return createUserSchema.validate(req.body)
.catch(validationError => {
res.sendStatus(400);
throw 'abort';
});
}).then(validatedUser => {
// if an error was thrown before, this code won't be executed
// accepts a hash of inputs and stores it in a database
return createUser({
username: validatedUser.username,
password: validatedUser.password
}).catch(error => {
// if an error was previously thrown from `createUserSchema.validate`
// this code won't execute
res.sendStatus(500);
throw 'abort';
});
}).then(() => {
// can put in even more code here
}).then(() => {
// it was not aborted
res.sendStatus(200);
}).catch(() => {
// it was aborted
});
You can skip the Promise.resolve().then() wrapping, but it's included for illustrative purposes of the general pattern of subdividing each task and its error handling.

Conditional chaining of promises

I want to chain my promises depending upon if the previous call was resolved or rejected. I am making a call to server in all promises.
So, I am writing it like-
apiServices.patientsSearch(id)
.then(function(data){
return callback(null,data);
},function(err){
return apiServices.studiesSearch(id);
}).then(function(data){
return callback(null,data);
},function(){
return apiServices.seriesSearch(id);
}).then(function(data){
return callback(null,data);
})
.catch(function(err){
return callback(false,err);
});
As every then returns a promise object, problem is that catch is always being called if any promise except the last one calls resolve. One way I am thinking is to check if err is empty and ignore it. Is it the right way to do it ?
I am using request module, if I set forever: true, I start getting-
{ [Error: socket hang up] code: 'ECONNRESET' }
With forever false, it works. Why my socket is still busy even after the request has ended ? As the next request will go only when reject is called, so socket should be free by that time.
You should only call the callback once. Don't pass it as the onfulfilled-handler after each promise, call it once in the end:
apiServices.patientsSearch(id).then(null, function(err){
return apiServices.studiesSearch(id);
}).then(null, function(){
return apiServices.seriesSearch(id);
}).then(function(data){
callback(null,data);
}, function(err){
callback(false,err);
});
or
apiServices.patientsSearch(id).catch(function(err){
return apiServices.studiesSearch(id);
}).catch(function(){
return apiServices.seriesSearch(id);
}).then(function(data){
callback(null,data);
}, function(err){
callback(false,err);
});
Of course, you shouldn't be calling any callbacks at all in promise-based code, so use this only if you have to interface with legacy code. Otherwise, don't take a callback parameter and just return the promise:
return apiServices.patientsSearch(id).catch(function(err){
return apiServices.studiesSearch(id);
}).catch(function(){
return apiServices.seriesSearch(id);
});

How to check this promise state, when programming a new promise? js

I am programming a new promise, it has many different conditions that call reject() or resolve() related to their state, also I know that the promise state will set with the first call to reject() | resolve().
My question is:
Is there any native (build-in) way to get the promise state?
The following is a demonstrative-code:
exports.addStatement = function (db, description, data) {
return new Promise(function (resolve, reject) {
validator.validateStatement(description, data)
.then(function (data) {
//......
if(cnd1)
resolve(res);
if(cnd2)
reject(err);
//......
//How to check if this promise is rejected or resolved yet?
})
.catch(function (err) {
reject(err);
})
})
};
You cannot directly examine the state of a promise. That's not how they work. You can use .then() or .catch() on them with a callback to get notified.
Or, in your specific case, you can probably change the way your code is structured to remove the anti-pattern of creating an unnecessary outer promise and switching your logic to if/else if/else.
Here's the cleaned up code:
exports.addStatement = function (db, description, data) {
return validator.validateStatement(description, data)
.then(function (data) {
//......
if(cnd1) {
// make res be the resolved value of the promise
return res;
} else if(cnd2) {
// make promise become rejected with err as the reason
throw err;
} else {
// decide what else to do here
}
})
})
};
If you couldn't make an if/else work for you, the above structure should still work because both the return and the throw terminate the execution of the .then() handler. So, the only code that continues after them is code that has not yet set the resolved/rejected value for the current promise so you don't have to look at the state of the promise to know that. If the code gets past the return and throw and is still executing, then neither of those was executed and the resolved/rejected value of the current promise is still unset.

Different variables for different request in nodejs

Say, I have the following code (Assume all undefined functions as async tasks which return promise):
app.post('/abc', (req, res) => {
var project;
getProject(req.body.projectId)
.then((_projectData) => {
//this data is required by other functions in this promise chain
project = _projectData;
})
.then(()=>{
return doSomething1(project)
})
.then(()=>{
return doSomething2(project)
})
.then(()=>{
return doSomething3(project)
})
.then(()=>{
return res.status(200).send('Everything done.')
})
.catch((err) => {
return res.status(500).send('Error occured.')
})
});
Now, when a request came, the project will be set to the project data related to that request. And doSomething1() will be called. Say, doSomething1() is taking a lot of time. Meanwhile second request came and getProject() called, and then the project variable would change. Now when 2nd request's doSomething1() is executing, 1st request's doSomething1() is completed and doSomething2() get called, but with the different project variable.
How to avoid this situation?
What is the best case for such cases?
(One way could be to break this promise chain into 2 promise chains, and put all the dependents in one chain, so that each request has different scope of project variable and all other functions are called in that scope:
app.post('/abc', (req, res) => {
getProject(req.body.projectId)
.then((_projectData) => {
//this data is required by other functions in this promise chain
var project = _projectData;
doSomething1(project).
.then(()=>{
return doSomething2(project)
})
.then(()=>{
return doSomething3(project)
})
.then(()=>{
return res.status(200).send('Everything done.')
})
.catch((err) => {
return res.status(500).send('Error occured.')
})
})
.catch((err) => {
return res.status(500).send('Error occured.')
})
});
But this doesn't seems good, as if the promise chain is big, and there are more dependencies (like output of doSomething1() as input of doSomething3()), the promise chains won't work as they are designed to.
Another way could be to pass the project variable via each promise resolve, but it would not be possible, as it might be 3rd party module function, or even changing the return value of a function for that purpose would not justify the function re-usability.)
What am I missing here?

Categories