2

I am trying to implement an equivalent to bluebird's Promise.method using TypeScript with es6 promises.

Desired usage:

const stringify = promiseMethod(JSON.stringify) stringify(/* ... */) //Type checking available here, returns Promise<string> 

Nearest implementation:

const promiseMethod = function<T, U> (fn: (T) => U) { if (typeof fn !== "function") { throw new TypeError("Parameter is not a function:" + fn); } return <(T) => Promise<U>>function () { try{ var value = fn.apply(this, arguments); return Promise.resolve(value); } catch (error){ return Promise.reject(error); } }; }; 

The problem with the above implementation is the call site expects only one parameter when there may be many.

I can get compilable code if I change the parameter and return types to Function but then no type information is available for the parameter or return type.

1
  • 1
    TypeScript supports signature overloading. One easy way to do this, is just to define multiple overloads which each different number of arguments. Commented Jun 29, 2017 at 17:56

1 Answer 1

1

There's no way to do this in a 100% typesafe manner (AFAIK! Please point out if I missed something.)

In a perfect world, Typescript would support using a type param to stand in for an entire argument list. So you could do this:

function promiseMethod<T,R>(fn: (...args: T) => R) { 

But that's not allowed. The best you can do is (...args: Array<any>), which is pretty lame.

(There is some discussion about this feature, see github issues here and here.)


You could take the sledgehammer approach and overload the promiseMethod function, like this:

function promiseMethod<R>(fn: () => R): () => Promise<R>; function promiseMethod<R,A>(fn: (a: A) => R): (a: A) => Promise<R>; function promiseMethod<R,A,B>(fn: (a: A, b: B) => R): (a: A, b: B) => Promise<R>; // etc... function promiseMethod<R>(fn: (...args: Array<any>) => R) { // implementation } 

This might serve your needs but it does have a few issues:

  • You'll still be able to call promiseMethod with more args than your giant-est overload, and the typing in that situation will be leaky.
  • Also leaky if fn itself has overloads (see below).

Still, it's better than ...args: Array<any>. A lot of commonly used libraries (e.g. lodash) use this pattern, for lack of any better alternative.


If the function you're passing in has overloads... good luck. Typescript's support for function overloads is pretty shallow (by design, it seems). If you refer to an overloaded function without calling it (e.g. by passing it into your promiseMethod function as a callback), it seems the compiler just uses the type signature of the last(?) defined overload, and throws away the others. Not very good. Of course, this will only bite you if you're actually passing in overloaded functions.


Finally, my opinion. Unless you are upgrading a large JS codebase to Typescript, I would consider avoiding the promisifying pattern entirely if possible. Especially with async/await now fully supported (since TS 2.1), I don't think there's a use for it.

Sign up to request clarification or add additional context in comments.

3 Comments

I do not understand your final comment. In order to use async/await I need a Promise and unless I all the APIs I am working with provide promises, I will need to promisify things to be able to use async/await.
I guess it depends what you are doing. But if you call your methods (the ones you were going to pass into Promise.method) from within async functions, you wouldn't have to worry about wrapping their results/throws (which is what Promise.method is for, from what I see)
Ahh, that makes sense, thanks for elaborating! My case was different in that I was building an abstraction over a mix of async and non-async APIs. Making everything return a Promise allowed callers to see a common interface.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.