# Errors without try/catch

> safe() and createSafeClient() return result tuples with typed, narrowable errors.

`safe()` wraps a call and resolves to a result instead of rejecting. Destructure it as a tuple, or read it as an object:

```ts
import { safe } from '@trpc12/trpc/client';

const [post, error] = await safe(client.post.byId.query({ id: '1' }));
const created = await safe(client.post.create.mutate({ title: 'Hi' }));
if (!created.error) console.log(created.data.title);
```

`createSafeClient(client)` returns a client whose every call is safe:

```ts
import { createSafeClient } from '@trpc12/trpc/client';

const safeClient = createSafeClient(client);
const [p, e] = await safeClient.post.byId.query({ id: '1' });
```

## Narrowing

`error.defined` is true for errors in the procedure's union, whether returned, failed or declared on the server ([Errors](https://v12.trpc.io/docs/server/errors.md)), and for errors links declare ([Custom links](https://v12.trpc.io/docs/client/custom-links.md#errors-and-unsupported-types)). Narrow on `code` next, and `data` takes that error's type:

```ts
const [created, err] = await safe(client.post.create.mutate({ title: '' }));
if (err?.defined) {
  err.code;
  // (property) TrpcError<TCode extends string = string, TData = unknown>.code: "BAD_REQUEST" | "UNAUTHORIZED"
}
```

It is false for anything else: undeclared thrown errors, network failures (`NETWORK_ERROR`), aborts (`CLIENT_CLOSED_REQUEST`). `isDefinedError(error)` and `isTrpcError(error)` check a caught value.
