Improve error handling in replication

Deprecate `Change.handleError`, it was used inconsistenly for a subset
of possible errors only. Rework all `Change` methods to always report
all errors to the caller via the callback.

Rework `PersistedModel` to report change-tracking errors via the
existing method `PersistedModel.handleChangeError`. This method
can be customized on a per-model basis to provide different error
handling.

The default implementation emits `error` event on the model class,
users can attach an event listener that can provide a custom error
handler.

NOTE: Unhandled `error` events crash the application by default.
This commit is contained in:
Miroslav Bajtoš 2015-03-26 17:14:41 +01:00
parent 6640f8a082
commit 63e2f4b134
2 changed files with 44 additions and 21 deletions

View File

@ -9,6 +9,7 @@ var CJSON = {stringify: require('canonical-json')};
var async = require('async');
var assert = require('assert');
var debug = require('debug')('loopback:change');
var deprecate = require('depd')('loopback');
/**
* Change list entry.
@ -73,18 +74,43 @@ module.exports = function(Change) {
*/
Change.rectifyModelChanges = function(modelName, modelIds, callback) {
var tasks = [];
var Change = this;
var errors = [];
modelIds.forEach(function(id) {
tasks.push(function(cb) {
var tasks = modelIds.map(function(id) {
return function(cb) {
Change.findOrCreateChange(modelName, id, function(err, change) {
if (err) return Change.handleError(err, cb);
change.rectify(cb);
if (err) return next(err);
change.rectify(next);
});
function next(err) {
if (err) {
err.modelName = modelName;
err.modelId = id;
errors.push(err);
}
cb();
}
};
});
async.parallel(tasks, function(err) {
if (err) return callback(err);
if (errors.length) {
var desc = errors
.map(function(e) {
return '#' + e.modelId + ' - ' + e.toString();
})
.join('\n');
var msg = 'Cannot rectify ' + modelName + ' changes:\n' + desc;
err = new Error(msg);
err.details = { errors: errors };
return callback(err);
}
callback();
});
async.parallel(tasks, callback);
};
/**
@ -217,7 +243,7 @@ module.exports = function(Change) {
var model = this.getModelCtor();
var id = this.getModelId();
model.findById(id, function(err, inst) {
if (err) return Change.handleError(err, cb);
if (err) return cb(err);
if (inst) {
cb(null, Change.revisionForInst(inst));
} else {
@ -459,6 +485,9 @@ module.exports = function(Change) {
};
Change.handleError = function(err) {
deprecate('Change.handleError is deprecated, ' +
'you should pass errors to your callback instead.');
if (!this.settings.ignoreErrors) {
throw err;
}

View File

@ -1038,9 +1038,8 @@ PersistedModel.createUpdates = function(deltas, cb) {
Model.findById(change.modelId, function(err, inst) {
if (err) return cb(err);
if (!inst) {
console.error('missing data for change:', change);
return cb &&
cb(new Error('missing data for change: ' + change.modelId));
cb(new Error('Missing data for change: ' + change.modelId));
}
if (inst.toObject) {
update.data = inst.toObject();
@ -1343,8 +1342,7 @@ PersistedModel.enableChangeTracking = function() {
function cleanup() {
Model.rectifyAllChanges(function(err) {
if (err) {
console.error(Model.modelName + ' Change Cleanup Error:');
console.error(err);
Model.handleChangeError(err, 'cleanup');
}
});
}
@ -1359,8 +1357,7 @@ function rectifyOnSave(ctx, next) {
function reportErrorAndNext(err) {
if (err) {
console.error(
ctx.Model.modelName + '.rectifyChange(s) after save failed:' + err);
ctx.Model.handleChangeError(err, 'after save');
}
next();
}
@ -1378,8 +1375,7 @@ function rectifyOnDelete(ctx, next) {
function reportErrorAndNext(err) {
if (err) {
console.error(
ctx.Model.modelName + '.rectifyChange(s) after delete failed:' + err);
ctx.Model.handleChangeError(err, 'after delete');
}
next();
}
@ -1426,11 +1422,9 @@ PersistedModel.rectifyAllChanges = function(callback) {
* @param {Error} err Error object; see [Error object](http://docs.strongloop.com/display/LB/Error+object).
*/
PersistedModel.handleChangeError = function(err) {
if (err) {
console.error(Model.modelName + ' Change Tracking Error:');
console.error(err);
}
PersistedModel.handleChangeError = function(err, operationName) {
if (!err) return;
this.emit('error', err, operationName);
};
/**