DEV Community

Cover image for Typescript Basics: Inferences
Shane
Shane

Posted on

Typescript Basics: Inferences

Say that we have an app that is leveraging a cache, and we want to write a little helper function that can take an API request an arbitrary string for a key, and check to see if data already exists for that key in the cache, if so just skip making the request and return the value from the cache, otherwise make the request, store the result, and then return it. We might write something like this:

const checkOrFetch = async(key, fetcher) => {
  try {
    // use key to check cache
    const cachedData = await checkCache(key);

    // if no cached data, run fetcher, cache result, and return
    if(cachedData) {
      return cachedData
    } else {
      const fetcherResult = await fetcher();
      addToCache(fetcherResult);
      return fetcherResult
    }
  } catch (error) {
    // do something with error
    return null
  }
}
Enter fullscreen mode Exit fullscreen mode

In the above example, our two arguments are implicitly typed as any, so wherever this helper is used, we leave ourselves open to possible errors when a dev assumes they're going to get back something from this function they may not actually end up with. However, with a very little bit of modification, we can not only make this function type-safe, but even relieve some of the typing work from the code that will implement this method, all we need to do is add some generic typing to the signature.

const checkOrFetch = async <FetcherReturnType>(
  key: string,
  fetcher: () => Promise<FetcherReturnType>
): Promise<FetcherReturnType | null> => {
 ...
}
Enter fullscreen mode Exit fullscreen mode

In the example the name FetcherReturnType is completely arbitrary, it is essentially a variable name. (In the wild, you'll often see single capital letters used like T for type, K for key, but let's not fear verbosity, and instead embrace clarity.) This small change does some pretty cool stuff. Now, as long as the fetcher that is passed as the second argument is typed (even implicitly!) our checkOrFetch function will be able to let the developer that is using it make sure their code is kosher. It's a bit backward from how we tend to think of functions and their variables, but what the syntax in the example means is something like:

In this function I'm writing, I know I'm gonna need to know about a type, it's the type of the value that it'll be returning, I just can't know what that is because it will be dependent on the argument passed to it, so let's save a place for it. We'll call it FetcherReturnType. At the end of the signature we make that note that whatever FetcherReturnType ends up being, we're gonna return a Promise that resolves to it, but also maybe null if things go pear-shaped.

The magic happens with the second argument. I don't know exactly what it's going to be, but I do know I need it to be an async function that resolves to something. Since Typescript is pretty good at inferring what a return type is from a function, all the user of this checkOrFetch method needs to do is pass the function in, and Typescript takes care of the rest. Let's look at some examples. First, a handful of needlessly async functions that really have no reason to exist but for this tutorial:

const numberFetcher = async() => 1;
const stringFetcher = async() => 'string';
const boolFetcher = async() => true;
const objectFetcher = async() => ({
  foo: 'bar'
});

const getNumber = async() => checkOrFetch('numberKey', numberFetcher);
const getString = async() => checkOrFetch('stringKey', stringFetcher);
const getBool = async() => checkOrFetch('boolKey', boolFetcher);
const getObject = async() => checkOrFetch('objectKey', objectFetcher);
Enter fullscreen mode Exit fullscreen mode

If we hover over the getters in our IDE, we can see that Typescript is making sure we know what we can expect back:

When we hover over getObject, our IDE knows it will return a Promise that resolves to our type.

When we hover over getObject, our IDE knows it will return a Promise that resolves to our type.This is just scratching the surface of what Typescript can do, but once you see how it analyzes your code and infers types for you, you'll start to wonder how you ever lived without it.

Top comments (0)