Reputation: 8500
In Node.js, I need to read a file and validate it's contents, all in async. I m using Node.js 6.6, bluebird 3.4.6
Example code:
// pseudo function to read file contents - resolves when 'flag' is true, rejects when 'flag' is false.
function readFile(flag) {
return new Promise(function (resolve, reject) {
console.log('Reading file...');
if (flag) {
resolve('File contents');
} else {
reject('readFile error');
}
});
}
// pseudo function to validate file contents - resolves when 'flag' is true, rejects when 'flag' is false.
function validate(fileContents, flag) {
return new Promise(function (resolve, reject) {
console.log('Validating file: ', fileContents);
if (flag) {
resolve('Validate passed');
} else {
reject('validation failed');
}
});
}
readFile(false)
.then(function (fileContents) {
console.log('Successfully read the file:', fileContents);
return fileContents;
})
.catch(function (fileReadErr) {
console.log('Failed to read the file:', fileReadErr);
throw fileReadErr; // or, return Promise.reject(err);
})
.then(function (fileContents) {
return validate(fileContents, false);
})
.then(function (result) {
console.log('Successfully validated the file:', result);
})
.catch(function (err) {
console.log('Failed to validate the file:', err);
})
;
<script src="https://cdn.jsdelivr.net/bluebird/3.4.6/bluebird.min.js"></script>
The above code will print
Reading file...
Failed to read the file: readFile error
Failed to validate the file: readFile error
The above promise chain roughly translates to below sync code:
try {
let fileContents;
try {
fileContents = readFile(false);
console.log('Successfully read the file:', fileContents);
} catch (e) {
console.log('Failed to read the file:', e);
throw e;
}
let validationResult = validate(fileContents, false);
console.log('Successfully validated the file:', validationResult);
} catch (err) {
console.log('Failed to validate the file:', err);
}
And, throwing or rejecting in the first catch
method will still invoke the 2nd catch
method.
My question: Is there any way to break the chain once the file reading is failed? My objective is to return different HTTP status codes (file read error: 500, validation failed: 400) from an express.js route.
I know a solution using non-standard specialized catch
method, but that requires special handling. In the sense, I need to throw errors or need some filtering key in the error object and both of which are not in my hands, and involves some work to achieve it. This solution is mentioned in bluebird docs & here: Handling multiple catches in promise chain
Upvotes: 7
Views: 7084
Reputation: 161
Maybe more people could get the same problem. I personally don't think this is the best way to do this, because then you have your app throwing pseudo error, which could mistakenly be processed by other error processing on your server. But it works like you proposed:
// pseudo function to read file contents - resolves when 'flag' is true, rejects when 'flag' is false.
function readFile(flag) {
return new Promise(function (resolve, reject) {
console.log('Reading file...');
if (flag) {
resolve('File contents');
} else {
throw new Error ('errorReading');
}
});
}
// pseudo function to validate file contents - resolves when 'flag' is true, rejects when 'flag' is false.
function validate(fileContents, flag) {
return new Promise(function (resolve, reject) {
console.log('Validating file: ', fileContents);
if (flag) {
resolve('Validate passed');
} else {
throw new Error ('validationFailed');
}
});
}
readFile(false)
.then(function (fileContents) {
console.log('Successfully read the file:', fileContents);
return fileContents;
})
.then(function (fileContents) {
return validate(fileContents, false);
})
.then(function (result) {
console.log('Successfully validated the file:', result);
})
.catch((error) => {
console.log(error.name);
console.log(error.message);
if (error.message === 'errorReading'){
console.log('error 500 - File could\'d be read');
// Maybe more custom error processing here
//return res.status(500).send(JSON.stringify({
// 'status' : 'error',
// 'message' : 'File could\'d be read'
//}));
} else if (error.message=== 'validationFailed'){
console.log('error 500 - Validation not OK');
// Maybe more custom error processing here
//return res.status(500).send(JSON.stringify({
// 'status' : 'error',
// 'message' : 'Validation not OK'
//}));
} else {
console.log('error 500 - Some really bad stuff!');
//return res.status(500).send(JSON.stringify({
// 'status' : 'error',
// 'message' : 'Some really bad stuff!',
// 'errorMessage': error
//}));
}
});
<script src="https://cdn.jsdelivr.net/bluebird/3.4.6/bluebird.min.js"></script>
Please note that I commented out the res.send
of express to avoid errors on the processing of this snippet!
Upvotes: 1
Reputation: 19288
The simplest solution by far is to use what I call "insulated catches". ie, a pattern in which each .catch()
is a specialist, associated with a particular step in the overall process, and the main chain comprises only .thens (and eventually a single, terminal catch).
Also, it is useful in this kind of circumstance to convey added information down the error path by re-throwing Error objects with added properties. This avoids the need for custom Errors.
Promise.resolve()
.then(function() {
return readFile(false)
.then(function (fileContents) {
console.log('Successfully read the file:', fileContents);
return fileContents;
})
.catch(function (error) {
error.code = 521; // or whatever
error.customMessage = 'Failed to read the file';
throw error;
})
})
.then(function (fileContents) {
return validate(fileContents, false)
.then(function (result) {
console.log('Successfully validated the file:', result);
return fileContents;
})
.catch(function (error) {
error.code = 522; // or whatever
error.customMessage = 'Failed to validate the file';
throw error;
});
})
.catch(function(error) { // terminal catch.
console.log(error);
// It's possible for unaugmented errors to reach this point,
// so be sure to test for the extra properties before trying to use them.
if(error.code) {...}
if(error.customMessage) {...}
// Note also that the original error.message is still intact.
});
The initial Promise.resolve()
isn't strictly necessary, but helps keep everything else symetrical.
This will work with any Promises/A+ lib. Bluebird-sugar is not required.
Upvotes: 5
Reputation: 10496
As far as i understand what you want to achieve i would suggest always using one single catch block (when can be avoided introducing nesting in promise logic which is totally ok in few use cases, but should be avoided when it can be, because you will potentially ends up with promise hell with indentation all around)
Can you handle all errors in your functions readFile
, validate
in uniform way like:
const error = new Error('something bad happened')
error.status = 500
return reject(error)
Then you could do you handle error logic within one single catch block based on status
such as res.status(err.status || 500).json(...)
Upvotes: 0
Reputation: 15351
You can create custom Error types like so:
ReadFileError = function() {};
ReadFileError.prototype = Error.prototype;
ValidationError = function() {};
ValidationError.prototype = Error.prototype;
Then, you can throw
from a Promise instead of rejecting:
function validate(fileContents, flag) {
return new Promise(function (resolve, reject) {
console.log('Validating file: ', fileContents);
if (flag) {
resolve('Validate passed');
} else {
throw new ReadFileError('readFile error');
}
});
}
Then you can catch different errors based on their types:
readFile(false)
.then(function (fileContents) {
console.log('Successfully read the file:', fileContents);
return fileContents;
})
.then(function (fileContents) {
return validate(fileContents, false);
})
.then(function (result) {
console.log('Successfully validated the file:', result);
})
.catch(ReadFileError, function (err) {
console.log(..., err);
})
.catch(ValidationError, function (err) {
console.log(..., err);
})
catch(function(err) {
...
});
Upvotes: 3