Immediately convert validation errors to Result<T>
, as soon as possible. Result<T>
would be the sync version of promises, with chain
having a similar role to then
, and .orElse
having a similar role to .catch
.
Assuming even crop
was converted to not throw an exception but return a Result<T>
:
var Transforms = {};
class ImageCropper() {
constructor(range) {
this._range = range
}
applyTo(image) {
var r = this._range
return crop(image, r.x1, r.y1, r.x2, r.y2)
}
}
Transforms.ImageCropper = ImageCropper;
class FacedetectCropper extends ImageCropper {
autoDetectFace(image) {
// runs costly algorithm and returns face range
}
applyTo(image) {
this._range = this.autoDetectFace(image);
return super.applyTo(image);
}
}
Transforms.FacedetectCropper = FacedetectCropper;
applyTransforms propagates the Result
function applyTransforms(transforms, image) {
var result = Result.of(image); // wrap in result
transforms.forEach(transform => {
result = result.chain(image => transform.applyTo(result))
})
return result;
}
Finally the response handler is this:
// Assume that we already passed through basic request validator for request.body params
// So all transforms are of valid type and have valid options for that route.
function applyUserTransforms(request, response) {
var transforms = request.body.transforms.map(t => new Transforms[t.type](t.options)));
applyTransforms(transforms, request.body.image)
.chain(image => response.answer(200, {..content type..}, image))
.orElse(error => {
if (error.type == 'ValidationError') {
response.answer(400, "This combination of transforms is not acceptable because: " + error.message);
} else {
logger.recordError(e);
response.answer(500, "Unexpected error");
}
});
}
Of course, this doesn't qualify as "check your inputs before you pass them".
Note: An interesting observation is that this code looks precisely the same regardless of whether it executes synchronously or asynchronously - even the sequential execution of the transformations :D