DEV Community

Gio
Gio

Posted on

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 ;)

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 :)