Skip to main content

Wrapper function

A wrapper function is a function that wraps another function or a piece of code in order to add extra behaviour, control, or a simpler call. Put simply, it is a layer around a function that breaks nothing but adds something: logging, validation, error handling, caching and so on.

Theory

TL;DR

  • A wrapper takes a function and returns a new function that calls the original inside itself.
  • It is a higher-order function, the classic way to add cross-cutting behaviour.
  • The original function is not modified, so it can still be used without the wrapper.
  • Typical jobs: logging, metrics, try/catch, permission checks, caching, throttling.
  • The same pattern underlies decorators and middleware.

Quick example

javascript
function greet(name) { return `Hello, ${name}!`; } function withLogging(fn) { return function (...args) { console.log(`Calling ${fn.name} with arguments:`, args); const result = fn(...args); console.log('Result:', result); return result; }; } const loggedGreet = withLogging(greet); loggedGreet('Maria');

Output:

text
Calling greet with arguments: [ 'Maria' ] Result: Hello, Maria!

Here withLogging is the wrapper function that adds extra behaviour on top of the plain greet function.

The core idea

You take an existing function or operation and create a new one that calls it inside itself, but with something extra around the call. For example:

  • add logging before the call;
  • measure execution time;
  • check the arguments;
  • handle errors with try/catch.

To stay transparent, the wrapper should accept ...args, return the original's result and, when a correct this matters, call the function through fn.apply(this, args).

Error handling

javascript
function riskyOperation() { throw new Error('Something went wrong!'); } function safeWrapper(fn) { return function (...args) { try { return fn(...args); } catch (err) { console.error('Error while calling the function:', err.message); return null; } }; } const safeOp = safeWrapper(riskyOperation); safeOp(); // Error while calling the function: Something went wrong!

Now even if the inner function throws, the surrounding code keeps working.

Measuring execution time

javascript
function heavyTask() { for (let i = 0; i < 1e7; i++) {} } function measureTime(fn) { return function (...args) { const start = performance.now(); fn(...args); const end = performance.now(); console.log(`Execution time: ${(end - start).toFixed(2)}ms`); }; } const measuredTask = measureTime(heavyTask); measuredTask(); // Execution time: 12.34ms

An asynchronous wrapper

For an async function the wrapper has to be asynchronous too, otherwise try/catch will not catch a rejected promise.

javascript
async function fetchData() { const res = await fetch('https://api.example.com/data'); return res.json(); } function withErrorHandling(fn) { return async function (...args) { try { return await fn(...args); } catch (e) { console.error('Request failed:', e); return null; } }; } const safeFetch = withErrorHandling(fetchData); safeFetch();

A wrapper as a decorator: permission checks

javascript
function authWrapper(fn) { return function (user, ...args) { if (!user.isAdmin) { throw new Error('Access denied'); } return fn(user, ...args); }; } function deletePost(user, postId) { console.log(`Post ${postId} deleted`); } const deletePostWithAuth = authWrapper(deletePost); deletePostWithAuth({ isAdmin: true }, 42); // Post 42 deleted deletePostWithAuth({ isAdmin: false }, 42); // Error: Access denied

Why wrapper functions are useful

GoalExample
Extending functionalityAdd logging, metrics, caching
SecurityCheck access rights or a token
Error handlingWrap risky operations in try/catch
ReuseBuild one behaviour template for many functions
Functional programmingUse higher-order functions

Summary table

PropertyDescription
What it isA function that calls another one inside itself
GoalAdd or change behaviour without touching the original
TypeHigher-order function
Typical examplesLogging, caching, validation, try/catch, metrics
BenefitsReusable, safe, readable

Common mistakes

  • Not returning the original function's result: the wrapper swallows the value and the call suddenly yields undefined.
  • Wrapping an async function with a synchronous wrapper: the error becomes an unhandled promise rejection and try/catch stays silent.
  • Losing this: when wrapping an object's method, call fn.apply(this, args) instead of fn(...args).
  • Forgetting that the new function has a different fn.name and fn.length, so code that reads the name or the arity breaks.
  • Stacking wrappers without restraint: five layers around one function make the stack trace unreadable and hide where the error really came from.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.