views:

2229

answers:

2

I'm writing a global error handling "module" for one of my applications.

One of the features I want to have is to be able to easily wrap a function with a Try{} Catch{} block, so that all calls to that function will automatically have the error handling code that'll call my global logging method. (To avoid polluting the code everywhere with try/catch blocks).

This is, however, slightly beyond my understanding of the low-level functioning of Javascript, the .call and .apply methods, and the "this" keyword.

I wrote this code, based on Prototype's Function.wrap method:

Object.extend(Function.prototype, {
  TryCatchWrap: function() {
    var __method = this;
    return function() {
      try { __method.apply(this, arguments) } catch(ex) { ErrorHandler.Exception(ex); }
    }
  }
});

Which is used like this:

 function DoSomething(a, b, c, d) {
  document.write(a + b + c)
  alert(1/e);
 }

 var fn2 = DoSomething.TryCatchWrap();
 fn2(1, 2, 3, 4);

That code works perfectly. It prints out 6, and then calls my global error handler.

My question is... Will this break something when the function I'm wrapping is within an object, and it uses the "this" operator? I'm slightly worried since I'm calling .apply, passing something there, I'm afraid this may break something.

+12  A: 

Personally instead of polluting builtin objects I would go with a decorator technique:

var makeSafe = function(fn){
  return function(){
    try{
      return fn.apply(this, arguments);
    }catch(ex){
      ErrorHandler.Exception(ex);
    }
  };
};

You can use it like that:

function fnOriginal(a){
  console.log(1/a);
};

var fn2 = makeSafe(fnOriginal);
fn2(1);
fn2(0);
fn2("abracadabra!");

var obj = {
  method1: function(x){ /* do something */ },
  method2: function(x){ /* do something */ }
};

obj.safeMethod1 = makeSafe(obj.method1);
obj.method1(42);     // the original method
obj.safeMethod1(42); // the "safe" method

// let's override a method completely
obj.method2 = makeSafe(obj.method2);

But if you do feel like modifying prototypes, you can write it like that:

Function.prototype.TryCatchWrap = function(){
  var fn = this; // because we call it on the function itself
  // let's copy the rest from makeSafe()
  return function(){
    try{
      return fn.apply(this, arguments);
    }catch(ex){
      ErrorHandler.Exception(ex);
    }
  };
};

Obvious improvement will be to parameterize makeSafe() so you can specify what function to call in the catch block.

Eugene Lazutkin
OK, so besides the preference of polluting or not...Your last code snippet looks identical to mine.Should I understand from this that my code does work with objects, and the "this" keyword?Thanks!
Daniel Magliola
Yes: you pass both "this" and original arguments to the wrapped method. But you don't return its result, making wrapping incomplete.But it doesn't matter if you wrap a function that doesn't return a value.
Eugene Lazutkin
A: 

As far as polluting the namespaces, I'm actually going to pollute them some more... Since everything that happens in JS is initiated by an event of some kind, I'm planning to call my magical wrapper function from within the Prototype Event.observe() method, so I don't need to call it everywhere.

I do see the downsides of all this, of course, but this particular project is heavily tied to Prototype anyway, and I do want to have this error handler code be as global as possible, so it's not a big deal.

Thanks for your answer!

Daniel Magliola
Besides unintended name clashes, polluting builtin objects can lead to strange errors on the language level. That's why working on new JavaScript standard revived discussions about "closing" builtin objects to modifications => future-proof code should avoid this technique.
Eugene Lazutkin