Изменение типа, если не null

Описание

Очень часто случается, что нужно из переменной типа T достать какое-то свойство типа U.
Иногда случается такое с переменной типа T | null. В таком случае, если значение null мы возвращаем null, в противном случае достаем переменную типа U. То есть результат может быть U | null.

Реализация

Теперь эту же логику пытаюсь реализовать как функцию. Примерно так правда?

function map<T, U>(value: T, callback: (object: NonNullable<T>) => NonNullable<U>): U

... ну как бы чтобы использовать вот так:

type Nullable<T> = T | null;

let text: Nullable<string> = `Hello world`;
const size: Nullable<number> = map<Nullable<string>, Nullable<number>>(text, nonNullableText => nonNullableText.length);

В таком случае делаю простейшую реализацию:

function map<T, U>(value: T, callback: (object: NonNullable<T>) => NonNullable<U>): U {
    if (value === null || value === undefined) return value; // null | undefined
    else return callback(value); // NonNullable<U>
} // NonNullable<U> | null | undefined тот же самый U

Получаю ошибку Type 'T' is not assignable to type 'U'. в части return value.

Вопрос

Нуу, по идеи ошибка логичная, но в какой части логики я ошибся?
Как...

function map<T, U>(value: T, callback: (object: NonNullable<T>) => NonNullable<U>): U

...правильно реализовать?


Дополнительно

Заголовок можете изменить. Не смог правильно описать ошибку.


Ответы (1 шт):

Автор решения: nörbörnën

Функция с типами:

function example<
  T,
  CB extends (obj: NonNullable<T>) => unknown,
  R = T extends NonNullable<T> ? ReturnType<CB> : null
>(input: T, cb: CB): R {
  return (
    input === null || input === undefined
      ? null
      : cb(input)
  ) as R;
}

const q1 = example(null, () => {});
const q2 = example('hello world', (str) => str.replace(/\s/g, '').length);
const q3 = example({ a: null, b: null }, (obj) => Object.keys(obj).join('/'));

Типы переменных в которых хранится результат выполнения:

const q1: null
const q2: number
const q3: string
[LOG]: null 
[LOG]: 10 
[LOG]: "a/b"
→ Ссылка