Wrapping a callback-based class to an async one
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.
Introduction
When a class exposes callback-based methods, the cleanest way to make it usable with async and await is to wrap each callback method in a Promise. The important details are preserving this, translating callback errors into rejected promises, and deciding how to expose multiple callback results.
The Basic Conversion Pattern
Suppose you start with an older API like this:
That API is not wrong, but it is awkward to compose because every call needs a nested callback.
The direct wrapper is a promise:
Once the method returns a promise, consumers can use await naturally.
Preserve the Original Instance Correctly
The most common wrapping bug is losing this. If the original method depends on instance state, extracting it without binding can break it.
This is unsafe:
This is safe because the method is invoked on the original instance:
If you need to store the function first, bind it.
Mapping Callback Shapes
Not every callback uses the Node-style (error, result) convention. Some APIs call back with only a result, and others provide multiple success values.
For multiple values, resolve an object or array:
That makes the async API predictable instead of pretending the callback had only one result.
Wrapper Class or Utility Function
If you are modernizing a whole class, a wrapper class keeps the old and new interfaces separate. If you only need one or two methods, standalone utility functions may be enough.
Use a wrapper class when:
- many methods need conversion
- you want to hide the callback API entirely
- the async version should become the main public interface
Use a helper function when the migration is small. A small internal helper such as toPromise(fn) can also reduce repetition, but only when the callback signature is consistent enough to wrap safely.
Common Pitfalls
The biggest mistake is forgetting to reject the promise on callback errors. That creates async methods that hang forever instead of failing.
Another mistake is losing the original this binding when calling the legacy method.
A third mistake is wrapping a callback API that can fire multiple times as if it were a one-shot promise. Promises represent one final result, so event-style callbacks need a different abstraction such as an async iterator or event emitter wrapper.
Summary
- Wrap callback-based methods in
Promiseto make them usable withasyncandawait. - Preserve the original instance context when calling legacy methods.
- Convert callback errors into rejected promises.
- For multiple success values, resolve an object or array instead of dropping data.
- Use wrapper classes for broader migrations and utility functions for isolated conversions.
Related reading
- Wrapping ManualResetEvent as awaitable task
- Write a well designed async / non-async API
- Writing a thread safe modular counter in Java
- Writing an asynchronous process that can be awaited
- Writing and Reading file async
- XMLHttpRequest used to find quicker server
- writing tfrecord with multithreading is not fast as expected
- Writing to a TextBox from another thread?
.png&w=3840&q=75)
Tackling System Design Interview Problems
A short course that equips you with the skills to approach system design interviews methodically.
Start the free courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.