Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Type 'string | undefined' is not assignable to type 'string'. Type 'undefined' is not assignable to type 'string'

I am declaring the following variables using TypeScript:

const BOT_PREFIX: string = process.env.PREFIX;
const BOT_TOKEN: string = process.env.TOKEN;

I get the following error:

Type 'string | undefined' is not assignable to type 'string'. Type 'undefined' is not assignable to type 'string'.ts(2322)

I can fix it by changing the data type to any, but i don't want to do that.

like image 996
Adonys Santos Avatar asked Dec 30 '22 14:12

Adonys Santos


1 Answers

Node's process.env has the following type definition (from this declaration file which uses a type defined here):

interface ProcessEnv {
    [key: string]: string | undefined
}

So, as far as TypeScript's compiler is concerned, any property you access under process.env will be of type string | undefined. That's a union, meaning that process.env.RANDOMKEY (for example) will either be a string or it will be undefined. Generally speaking this is the right type; the compiler has no idea which environment variables are actually set.

And so this is a problem:

const BOT_PREFIX: string = process.env.PREFIX // error!

and the compiler warns you that process.env.PREFIX might be undefined, so it's not safe to treat it as a string.

The way to deal with this depends on whether you want convenience or type safety, and what you want to see happen if your assumptions about process.env.PREFIX and process.env.TOKEN are incorrect.


For pure convenience, you probably can't beat the non-null assertion operator (!):

const BOT_PREFIX: string = process.env.PREFIX!; // okay
const BOT_TOKEN: string = process.env.TOKEN!; // okay

Here you are just telling the compiler that, even though it cannot verify this, you are sure process.env.PREFIX and process.env.TOKEN will be defined. This essentially just suppresses the compiler warning; it's still doing the same thing at runtime as your original code. And that means if you turn out to be wrong about your assertion, then you might run into problems at runtime that the compiler can't help you with:

BOT_PREFIX.toUpperCase(); // runtime error if BOT_PREFIX is undefined after all

So be careful.


On the other hand, you can try to make the code safer by handling the situation in which the environment variables you expect are not set. For example:

function getEnvVar(v: string): string {
    const ret = process.env[v];
    if (ret === undefined) {
        throw new Error("process.env." + v + " is undefined!");
    }
    return ret;
}

const BOT_PREFIX: string = getEnvVar("PREFIX");
const BOT_TOKEN: string = getEnvVar("TOKEN");

Here we've written a function called getEnvVar() which takes the name of the environment variable and returns its value, as long as that value is a string. If the environment variable is not defined, then a runtime error will be thrown. TypeScript understands via control flow analysis that the return type of getEnvVar() is string (and not string | undefined; the undefined possibility has been eliminated by the throw statement), and so you can safely assign the returned value to a variable of type string.

This code is obviously less convenient since it requires extra runtime code, but now instead of possibly lying to the compiler and having bizarre runtime behavior, you will get some immediate feedback at runtime if your assumptions are invalid.


Playground link to code

like image 131
jcalz Avatar answered Jan 02 '23 04:01

jcalz