DEV Community

Gio
Gio

Posted on

2 1

Implement a type-safe version of Node's Promisify in 7 lines of TypeScript

type Callback<A> = (args: A) => void;

const promisify = <T, A>(fn: (args: T, cb: Callback<A>) => void): ((args: T) => Promise<A>) =>
  (args: T) => new Promise((resolve) => {
    fn(args, (callbackArgs) => {
      resolve(callbackArgs);
    });
  });
Enter fullscreen mode Exit fullscreen mode

Check out this video that demonstrates type inference in action!

I'm using type-variables T and A to generecally implement this function over the original function's arguments, and the callback function's arguments.

Exercises:

  • Did I need to define an inline anonymous function for the second argument of fn? How else could I have invoked fn?

  • Note that my Callback type isn't a typical error-first callback like in a lot of node APIs (this is just because the functions I'm trying to promisify aren't error-first callbacks). So I'll leave it to you as an exercise to refactor my promisify function to reject when an error on an error-first callback is not null ;)

Tiugo image

Modular, Fast, and Built for Developers

CKEditor 5 gives you full control over your editing experience. A modular architecture means you get high performance, fewer re-renders and a setup that scales with your needs.

Start now

Top comments (3)

Collapse
 
rthwwhrt profile image
Max

Error-first callback example

type Callback<E, A> = (error: E, args: A) => void;

export const promisify =
  <T, E, A>(fn: (args: T, cb: Callback<E, A>) => void): ((args: T) => Promise<A>) =>
  (args: T) =>
    new Promise((resolve, reject) => {
      fn(args, (error, callbackArgs) => {
        if (error) {
          reject(error);
        }

        resolve(callbackArgs);
      });
    });

Enter fullscreen mode Exit fullscreen mode
Collapse
 
sfsr12 profile image
sfsr12

This is awesome, and I've been using it and just spent the better part of the day trying to wrap my head around how you would write a promisifyAll version of this that would promisify all of the properties of an object. Assuming they were all "promisifiable".

If you could give me any hint that points in the right direction I would be much obligied.

Thanks!

Collapse
 
_gdelgado profile image
Gio

This is quite tricky!

You will need to infer the type of function arguments.

See here:

stackoverflow.com/questions/518516...

You can then access each individual argument's type by accessing the index of the type:

const fn1 = (args: string, cb: Callback<string>): void => {
    setTimeout(() => {
     cb(null, 'example1')
    }, 1000)
}

// P is of type 'string' since 'string' is the 0'th argument
type P = Parameters<typeof fn1>[0]
Enter fullscreen mode Exit fullscreen mode

You'll also need a Mapped Type (docs on mapped types):

type CallbackObj = Record<string, (args: any, cb: Callback<any>) => void>

type PromisifiedObject<T extends CallbackObj> = {
  [P in keyof T]: (args: Parameters<T[P]>[0]) => Promise<????>; 
};
Enter fullscreen mode Exit fullscreen mode

Hope that helps. The ???? is there for you to figure out :)

Neon image

Next.js applications: Set up a Neon project in seconds

If you're starting a new project, Neon has got your databases covered. No credit cards. No trials. No getting in your way.

Get started →

👋 Kindness is contagious

Explore this insightful post in the vibrant DEV Community. Developers from all walks of life are invited to contribute and elevate our shared know-how.

A simple "thank you" could lift spirits—leave your kudos in the comments!

On DEV, passing on wisdom paves our way and unites us. Enjoyed this piece? A brief note of thanks to the writer goes a long way.

Okay