Java Optionals in TypeScript

Optionals in Java haben sich als starkes und wertvolles Konstrukt erwiesen. Java Entwickler die zu TypeScript wechseln, vermissen oftmals dieses Feature. Heute möchten wir uns ansehen, wie wir Optionals in TypeScript implementieren können. Ziel von Optionals ist es, unnötige null checks zu verhindern und den Code somit leserlicher und kürzer zu halten.

Um Optionals zu implementieren, sehen wir uns zunächst ein Beispiel Code aus der Java Welt an.

Optional<User> currentUser = getCurrentUserFromDatabaseById("213");
Optional<Address> address = currentUser.flatMap(User::getAddress);

Wenn wir obiges in TypeScript schreiben müssten, könnte dies wie folgt aussehen.

const currentUser: User | undefined = getCurrentUserFromDatabaseById("123");
// Prior to TypeScript 3.7
const address: Address | undefined = currentUser !== undefined ? currentUser.getAddress() : undefined;

// With TypeScript 3.7 and up
const address: Address | undefined = currentUser?.getAddress();

Die erste Möglichkeit sieht nicht wirklich schön aus und ist auch nicht besonders schön zu programmieren. Man möchte als Entwickler schließlich nicht ständig gegen das Typen-System arbeiten zu müssen. Wenn du also noch mit TypeScript 3.6 oder älter arbeitest, könnte die Implementierung von Optionals tatsächlich helfen.

const isAssigned = <T>(value: T | null | undefined): value is T => {
    return !isNil(value);
}

const isNil = <T>(value: T | null | undefined): value is null | undefined => {
    return value === undefined || value === null;
}

export class Optional<T> {
    public static empty<T>(): Optional<T> {
        return new Optional<T>(null);
    }

    public static ofNullable<T>(value: T | null | undefined): Optional<T> {
        return isAssigned(value) ? this.of<T>(value) : this.empty();
    }

    public static of<T>(value: T): Optional<T> {
        if (isNil(value)) {
            throw Error("Provided value can not be null or unassigned");
        }
        return new Optional<T>(value);
    }

    private constructor(private prop: T | null | undefined) {}

    public orElse<Y>(other: Y): Y | T {
        return isNil(this.prop) ? other : this.prop;
    }

    public orElseGet<Y>(other: () => Y): Y | T {
        return isNil(this.prop) ? other() : this.prop;
    }

    public map<Y>(toMap: (wrapped: T) => Y): Optional<Y> {
        return isNil(this.prop) ? Optional.empty<Y>() : Optional.of<Y>(toMap(this.prop));
    }

    public flatMap<Y>(toMap: (wrapped: T) => Y): Optional<Y> {
        return isNil(this.prop) ? Optional.empty<Y>() : Optional.ofNullable(toMap(this.prop));
    }

    public isPresent(): boolean {
        return isAssigned(this.prop);
    }

    public filter(predicate: (value: T) => boolean) {
        if (!this.isPresent()) {
            return this;
        }

        return predicate(this.prop!) ? this : Optional.empty();
    }
}

Das initiale Beispiel sieht mit dieser Optionals Implementierung dann so aus

const currentUser: Optional<User> = Optional.ofNullable(getCurrentUserFromDatabaseById("123"));
const address: Optional<Address> = currentUser.flatMap(user => user.getAddress());

Ist das nicht viel besser?

Es ist zwar nicht ganz so hipp wie Optional Chaining in TypeScript 3.7, aber immerhin eine große Hilfe für Ankömmlinge aus der Java Welt oder jene die, die TypeScript Version nicht inkrementieren können. Oder für Leute die gerne Sprachkonzepte aus anderen Sprachen ausprobieren :-)!

1250cookie-checkJava Optionals in TypeScript

Kommentar verfassen