Raven.wrap, that automatically wraps a function in try/catch and passes any caught errors to
Raven.captureException, Raven’s primary error reporting function.
In this article, we’ll learn how function wrappers are useful, how they’re written, and how they should be written. But first …
Why use function wrappers?
You’re probably familiar with
Function.prototype.bind, which returns a function that, when called, has its
this keyword set to the provided value:
You can think of
bind as producing a function wrapper whose purpose is to call the original function, with a small additional quirk: it permanently changes the value of
We can do the same thing manually, without
bind, but instead using a closure:
this across different functional scopes. Function wrappers produced by
Function.prototype.bind are a handy, commonly-used tool.
A function wrapper can be used to transparently record the duration of a function invocation. This can be helpful when profiling the performance of your application.
Note that this example only times the duration of synchronous code. If the wrapped function triggers an asynchronous callback (e.g. via
setTimeout), any time spent in that callback will not be captured.
In the example below, the
makeRoyalMixin function changes an object by wrapping that object’s
getName prototype method with a function that changes its output:
But wait, there’s more
Profiling and Mixins are just two simple examples. Here’s a few other useful applications:
- Code coverage – use function wrappers to detect if a function has been invoked during the execution of an application
- Hiding complexity – use function wrappers to provide a simpler API than the underlying code
- Cross-platform code – function wrappers are often used to smooth-out minor incompatibilities between an API on different platforms
- Partial applications – use a function wrapper to fix specific arguments to that function’s invocation
- Safely handling errors – our very first example, using function wrappers to transparently try/catch errors
Writing bulletproof function wrappers
Okay, we now know what function wrappers are, and how they’re commonly used. Now it’s time to write one. And not just any function wrapper – a wrapper that can wrap any conceivable function, invoked every which way, without breaking anything.
Use apply and arguments
Let’s say you want to wrap a known function, like
XMLHttpRequest.prototype.open. You look up the signature of
open on MDN, and learn that it has 5 arguments:
password. You write a wrapper for this function, passing the 5 declared arguments to the original function using
But there’s a problem with this implementation. It’s subtle, but you have changed the behavior of calling
open takes an optional number of arguments. At minimum, it can accept just
url. Or it can accept
async. Or it can accept all 5:
open implementation actually inspects the number of arguments passed.
Consider this hypothetical native implementation:
In the function wrapper we wrote, we have changed the value of
arguments.length as passed to the original
open method. By doing
.call(this, method, url, async, user, password), we have guaranteed that
arguments.length will always be 5, regardless of how many arguments were passed to the wrapper.
In the hypothetical native implementation above, this means the
_simpleRequest code path will never be reached; it always calls
arguments.length is always 5.
The solution: always pass an array of arguments to the wrapped function, matching the length of the arguments provided to the wrapper. If you need to edit one of the values, make a copy of the
arguments object, edit the affected value, and pass the copy.
Note that in the example above, we’ve changed from declaring 5 parameters (method, url, async, user, password) to just 2 (method and url). Why?
Like arrays and strings, function objects actually have a
length property. This property reports the arity of a function – the number of declared parameters in its function signature.
For example, in the code sample below,
foo function has an arity of 3, and
bar has an arity of 0 (no formal parameters):
XMLHttpRequest.prototype.open. Despite the documented number of variables being 5, the reported arity via the
length property is actually 2:
You might be wondering – why does this matter? Well, just as we saw earlier that some code branches differently because of
arguments.length, there is code out there that branches differently depending on the number of parameters declared in a function. If, in wrapping a function, we change its arity, we risk changing the behavior of code that inspects that function’s
Mocha test functions and the impact of arity
In Mocha, you declare a test function using the
it function, which accepts both a descriptive string (what the test does) and the test function as arguments.
Mocha also allows you to declare an asynchronous test function. In this version, the test function itself must declare a
done parameter, which will be passed a callback function to be invoked when the test function has finished. If an exception was thrown during the execution of the test function, the
done callback accepts an Error object as an argument.
Mocha is a pretty popular testing library, and it’s likely that many of you are familiar with its synchronous vs asynchronous test API. What you may not know, however, is that Mocha decides whether a test function is asynchronous by inspecting that test function’s
Here’s the relevant bit from Mocha’s source code:
Let’s say that you wrap a test function that is passed to Mocha’s test runner via
it. If in wrapping that test function, you simply pass arguments and don’t redeclare the
done variable, you will reduce its arity to 0 and Mocha will not consider the test function to be asynchronous. This will cause you serious grief as you try to figure out why the heck your test function doesn’t work anymore.
The purpose of showing you this Mocha code is to demonstrate that, yes, there is code out there that inspects the
length property of a function, and a failure to preserve arity when wrapping a function could result in broken code. So whenever you can, redeclare your wrapped variables and preserve arity.
Do I really need to do all this?
I’ll admit – a lot of the examples in this blog post are rare. It’s not often that you will encounter code that behaves differently depending on
Function.length. When writing function wrappers that operate on your own, known functions, it is unlikely that you will encounter such behavior.
But, if you’re a library author, or writing 3rd-party scripts that operate in an unknown environment, or want to really futureproof your code – it couldn’t hurt to safeguard against problematic behavior by using the techniques above when writing function wrappers.
Hopefully, with the skills you’ve learned today, you’ll know when and where to practice safe function wrapping. Good luck.