I see as used a lot in TypeScript, and it is one of my pet hates. It throws away type checking by asserting “I know better than the compiler”, which is often not true.
A common case is filtering a discriminated union array down to one variant. Say you have a chat app, people reach for an assertion like this:
type UpdateNewMessage = { type: "UpdateNewMessage"; text: string };
type UpdateUserJoined = { type: "UpdateUserJoined"; username: string };
type Update = UpdateNewMessage | UpdateUserJoined;
const allUpdates: Update[] = getUpdates();
const newMessageUpdates = allUpdates.filter(
(update) => update.type === "UpdateNewMessage",
) as UpdateNewMessage[];
This compiles, but you have forced the array to be a UpdateNewMessage[] even if your filter is wrong.
Instead, use a type predicate to let the compiler narrow safely:
type UpdateNewMessage = { type: "UpdateNewMessage"; text: string };
type UpdateUserJoined = { type: "UpdateUserJoined"; username: string };
type Update = UpdateNewMessage | UpdateUserJoined;
function isUpdateNewMessage(update: Update): update is UpdateNewMessage {
return update.type === "UpdateNewMessage";
}
const newMessageUpdates = allUpdates.filter(isUpdateNewMessage);
// inferred as UpdateNewMessage[]
The predicate keeps the check in one place, and you keep full type safety without a cast. These functions can live alongside your type declarations so your runtime checks stay aligned with your types.
Much more satisfying :D
More details: https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates