How to properly get inferred types of returned values in a wrapper function with jsdoc in vscode?

Viewed 1007

Here is some code (simplified) to understand better:

A wrapper to send requests

  • It does some common things when the request is sent and when it is completed.
  • It returns the result of the request (handler).
async function request (handler) {
  // common stuff
  try {
    const result = await handler()
    return result || true
  }
  catch (err) {
    doSomethingWithError(err)
    return err
  }
  finally {
  // common stuff
  }
}

/**
 * Imagine a simple service / api client
 */
const someApiClient = {
  /**
   * @returns {Promise<string[]>} 3 first letters
   */
  async getSomeData () {
    return ['a', 'b', 'c']
  }
}

/**
 * Just a function or method using the api client, that has types declared
 */
async function fetchMeSomeDataHandler () {
  return someApiClient.getSomeData()
}

const result = await request(() => fetchMeSomeDataHandler())

Expected

Here, I would expect vscode / jsdoc to infer that the type of "result" is string[] and even give me the description from the api client ("3 first letters").

But it isn't, it will give a type of any at the end.

What we get in vscode

If we follow the types picked up by vscode, we can see it's "losing the types little by little" until there is nothing left.

  1. Full type description picked up for the api client.

Full type description in the api client

  1. The type is known in the handler definition but we lost the client value description (3 first letters).

The type is known in the handler definition but we lost the client value description

  1. We lost the type information completely for the final result

We lost the type information completely

1 Answers

VS Code does try to infer the types of function parameters based on their usage. A simple example of this is:

function foo(a) {
    return a.b;
}

foo({ b: 3 })

Which results in foo having the signature function foo(a: any): any. Your code is a more complex example of this limitation.

The fix is to explicitly annotate the parameter type on request:

/**
 * @template T
 * @param {() => Promise<T>} handler 
 * @return {Promise<T>}
 */
async function request(handler) {
   ...
}

In this specific case, we also need the explicit @returns since the catch block does not return a value of type T (it returns an any since the error is untyped).

You can omit the @returns if request were written:

/**
 * @template T
 * @param {() => Promise<T>} handler 
 */
async function request(handler) {
    // common stuff
    const result = await handler()
    return result
}

Because VS Code can infer the return types of functions (but keep in mind that explicit types are useful in many cases, especially for functions that may return multiple different types)

After adding the explicit typings, result should have the correct type:

enter image description here

Related