diff --git a/lib/observer.js b/lib/observer.js index b10612d0..30327909 100644 --- a/lib/observer.js +++ b/lib/observer.js @@ -7,6 +7,7 @@ const async = require('async'); const utils = require('./utils'); +const debug = require('debug')('loopback:observer'); module.exports = ObserverMixin; @@ -232,7 +233,24 @@ ObserverMixin.notifyObserversAround = function(operation, context, fn, callback) function cbForWork(err) { const args = [].slice.call(arguments, 0); - if (err) return callback.apply(null, args); + if (err) { + // call observer in case of error to hook response + context.error = err; + self.notifyObserversOf('after ' + operation + ' error', context, + function(_err, context) { + if (_err && err) { + debug( + 'Operation %j failed and "after %s error" hook returned an error too. ' + + 'Calling back with the hook error only.' + + '\nOriginal error: %s\nHook error: %s\n', + err.stack || err, + _err.stack || _err + ); + } + callback.call(null, _err || err, context); + }); + return; + } // Find the list of params from the callback in addition to err const returnedArgs = args.slice(1); // Set up the array of results diff --git a/test/async-observer.test.js b/test/async-observer.test.js index b4c02b36..04301f81 100644 --- a/test/async-observer.test.js +++ b/test/async-observer.test.js @@ -325,6 +325,86 @@ describe('async observer', function() { } ); }); + + it('should call after operation hook on error', function(done) { + const context = { + req: {}, + }; + const operationError = new Error('The operation failed without result'); + let callCount = 0; + + function fail(context, done) { + process.nextTick(() => { + done(operationError); + }); + } + + TestModel.observe('after execute error', function(ctx, next) { + callCount++; + next(); + }); + + TestModel.notifyObserversAround('execute', context, fail, (err, ctx) => { + callCount.should.eql(1); + err.message.should.eql(operationError.message); + ctx.error.message.should.eql(operationError.message); + done(); + }); + }); + + it('should call after operation hook on error while overwriting error', function(done) { + const context = { + req: {}, + }; + const operationError = new Error('The operation failed without result'); + const overwriteError = new Error('Overwriting the original error'); + let callCount = 0; + + function fail(context, done) { + process.nextTick(() => { + done(operationError); + }); + } + + TestModel.observe('after execute error', function(ctx, next) { + callCount++; + next(overwriteError); + }); + + TestModel.notifyObserversAround('execute', context, fail, (err, ctx) => { + callCount.should.eql(1); + err.message.should.eql(overwriteError.message); + ctx.error.message.should.eql(operationError.message); + done(); + }); + }); + + it('should call after operation hook on error while allowing to change err', function(done) { + const context = { + req: {}, + }; + const operationError = new Error('The operation failed without result'); + let callCount = 0; + + function fail(context, done) { + process.nextTick(() => { + done(operationError); + }); + } + + TestModel.observe('after execute error', function(ctx, next) { + callCount++; + const err = ctx.error; + next(err, ctx); + }); + + TestModel.notifyObserversAround('execute', context, fail, (err, ctx) => { + callCount.should.eql(1); + err.message.should.eql(operationError.message); + ctx.error.message.should.eql(operationError.message); + done(); + }); + }); }); function pushAndNext(array, value) {