Skip to content

Error handling

Review how to handle errors (exceptions) generated by both your own Durable Object code as well as exceptions thrown by Durable Objects’ infrastructure (such as overloads or network errors).

How exceptions are thrown

Durable Objects can throw exceptions in one of two ways:

  • An exception can be thrown within the user code which implements a Durable Object class. The resulting exception will have a .remote property set to True in this case.
  • An exception can be generated by Durable Object’s infrastructure. Some sources of infrastructure exceptions include: transient internal errors, sending too many requests to a single Durable Object, and too many requests being queued due to slow or excessive I/O (external API calls or storage operations) within an individual Durable Object. Some infrastructure exceptions may also have the .remote property set to True — for example, when the Durable Object exceeds its memory or CPU limits.

Refer to Troubleshooting to review the types of errors returned by a Durable Object and/or Durable Objects infrastructure and how to prevent them.

Understanding stubs

A Durable Object stub is a client Object used to send requests to a remote Durable Object. To learn more about how to make requests to a Durable Object, refer to Create Durable Objects stubs and Access a Durable Objects from a Worker.

Example

Any uncaught exceptions thrown by a Durable Object or its infrastructure will be propagated to the callsite of the client. Catching these exceptions allows you to retry creating the Durable Object stub and sending requests.

import { DurableObject } from "cloudflare:workers";
export interface Env {
ErrorThrowingObject: DurableObjectNamespace;
}
export default {
async fetch(request, env, ctx) {
let userId = new URL(request.url).searchParams.get("userId") || "";
const id = env.ErrorThrowingObject.idFromName(userId);
// Retry behavior can be adjusted to fit your application.
let maxAttempts = 3;
let baseBackoffMs = 100;
let maxBackoffMs = 20000;
let attempt = 0;
while (true) {
// Try sending the request
try {
// Create a Durable Object stub for each attempt, because certain types of
// errors will break the Durable Object stub.
const doStub = env.ErrorThrowingObject.get(id);
const resp = await doStub.fetch("http://your-do/");
return Response.json(resp);
} catch (e: any) {
if (!e.retryable) {
// Failure was not a transient internal error, so don't retry.
break;
}
}
let backoffMs = Math.min(maxBackoffMs, baseBackoffMs * Math.random() * Math.pow(2, attempt));
attempt += 1;
if (attempt >= maxAttempts) {
// Reached max attempts, so don't retry.
break;
}
await scheduler.wait(backoffMs);
}
return new Response("server error", { status: 500 });
},
} satisfies ExportedHandler<Env>;
export class ErrorThrowingObject extends DurableObject {
constructor(state: DurableObjectState, env: Env) {
super(state, env);
// Any exceptions that are raised in your constructor will also set the
// .remote property to True
throw new Error("no good");
}
async fetch(req: Request) {
// Generate an uncaught exception
// A .remote property will be added to the exception propagated to the caller
// and will be set to True
throw new Error("example error");
// We never reach this
return Response.json({});
}
};