Skip to content

Commit 3e2a1e7

Browse files
authored
Update waitUntil documentation to document 30s limit (#27711)
1 parent b3cd5e6 commit 3e2a1e7

File tree

1 file changed

+65
-61
lines changed

1 file changed

+65
-61
lines changed

src/content/docs/workers/runtime-apis/context.mdx

Lines changed: 65 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,19 @@
22
pcx_content_type: configuration
33
title: Context (ctx)
44
head: []
5-
description: The Context API in Cloudflare Workers, including props, exports, waitUntil and
5+
description:
6+
The Context API in Cloudflare Workers, including props, exports, waitUntil and
67
passThroughOnException.
7-
88
---
99

10-
import {
11-
Tabs,
12-
TabItem,
13-
WranglerConfig
14-
} from "~/components";
10+
import { Tabs, TabItem, WranglerConfig } from "~/components";
1511

1612
The Context API provides methods to manage the lifecycle of your Worker or Durable Object.
1713

1814
Context is exposed via the following places:
1915

20-
* As the third parameter in all [handlers](/workers/runtime-apis/handlers/), including the [`fetch()` handler](/workers/runtime-apis/handlers/fetch/). (`fetch(request, env, ctx)`)
21-
* As a class property of the [`WorkerEntrypoint` class](/workers/runtime-apis/bindings/service-bindings/rpc) (`this.ctx`)
16+
- As the third parameter in all [handlers](/workers/runtime-apis/handlers/), including the [`fetch()` handler](/workers/runtime-apis/handlers/fetch/). (`fetch(request, env, ctx)`)
17+
- As a class property of the [`WorkerEntrypoint` class](/workers/runtime-apis/bindings/service-bindings/rpc) (`this.ctx`)
2218

2319
Note that the Context API is available strictly in stateless contexts, that is, not [Durable Objects](/durable-objects/). However, Durable Objects have a different object, the [Durable Object State](/durable-objects/api/state/), which is available as `this.ctx` inside a Durable Object class, and provides some of the same functionality as the Context API.
2420

@@ -72,26 +68,26 @@ To use `ctx.exports`, you must use [the `enable_ctx_exports` compatibility flag]
7268

7369
`ctx.exports` provides automatically-configured "loopback" bindings for all of your top-level exports.
7470

75-
* For each top-level export that `extends WorkerEntrypoint` (or simply implements a fetch handler), `ctx.exports` automatically contains a [Service Binding](/workers/runtime-apis/bindings/service-bindings).
76-
* For each top-level export that `extends DurableObject` (and which has been configured with storage via a [migration](/durable-objects/reference/durable-objects-migrations/)), `ctx.exports` automatically contains a [Durable Object namespace binding](/durable-objects/api/namespace/).
71+
- For each top-level export that `extends WorkerEntrypoint` (or simply implements a fetch handler), `ctx.exports` automatically contains a [Service Binding](/workers/runtime-apis/bindings/service-bindings).
72+
- For each top-level export that `extends DurableObject` (and which has been configured with storage via a [migration](/durable-objects/reference/durable-objects-migrations/)), `ctx.exports` automatically contains a [Durable Object namespace binding](/durable-objects/api/namespace/).
7773

7874
For example:
7975

8076
```js
8177
import { WorkerEntrypoint } from "cloudflare:workers";
8278

8379
export class Greeter extends WorkerEntrypoint {
84-
greet(name) {
85-
return `Hello, ${name}!`;
86-
}
80+
greet(name) {
81+
return `Hello, ${name}!`;
82+
}
8783
}
8884

8985
export default {
90-
async fetch(request, env, ctx) {
91-
let greeting = await ctx.exports.Greeter.greet("World")
92-
return new Response(greeting);
93-
}
94-
}
86+
async fetch(request, env, ctx) {
87+
let greeting = await ctx.exports.Greeter.greet("World");
88+
return new Response(greeting);
89+
},
90+
};
9591
```
9692

9793
In this example, the default fetch handler calls the `Greeter` class over RPC, like how you'd use a Service Binding. However, there is no external configuration required. `ctx.exports` is populated _automatically_ from your top-level imports.
@@ -106,22 +102,22 @@ Loopback Service Bindings in `ctx.exports` have an extra capability that regular
106102
import { WorkerEntrypoint } from "cloudflare:workers";
107103

108104
export class Greeter extends WorkerEntrypoint {
109-
greet(name) {
110-
return `${this.ctx.props.greeting}, ${name}!`;
111-
}
105+
greet(name) {
106+
return `${this.ctx.props.greeting}, ${name}!`;
107+
}
112108
}
113109

114110
export default {
115-
async fetch(request, env, ctx) {
116-
// Make a custom greeter that uses the greeting "Welcome".
117-
let greeter = ctx.exports.Greeter({props: {greeting: "Welcome"}})
111+
async fetch(request, env, ctx) {
112+
// Make a custom greeter that uses the greeting "Welcome".
113+
let greeter = ctx.exports.Greeter({ props: { greeting: "Welcome" } });
118114

119-
// Greet the world. Returns "Welcome, World!"
120-
let greeting = await greeter.greet("World")
115+
// Greet the world. Returns "Welcome, World!"
116+
let greeting = await greeter.greet("World");
121117

122-
return new Response(greeting);
123-
}
124-
}
118+
return new Response(greeting);
119+
},
120+
};
125121
```
126122

127123
</TabItem> <TabItem label="TypeScript" icon="seti:typescript">
@@ -130,25 +126,25 @@ export default {
130126
import { WorkerEntrypoint } from "cloudflare:workers";
131127

132128
type Props = {
133-
greeting: string
134-
}
129+
greeting: string;
130+
};
135131

136132
export class Greeter extends WorkerEntrypoint<Env, Props> {
137-
greet(name) {
138-
return `${this.ctx.props.greeting}, ${name}!`;
139-
}
133+
greet(name) {
134+
return `${this.ctx.props.greeting}, ${name}!`;
135+
}
140136
}
141137

142138
export default {
143-
async fetch(request, env, ctx) {
144-
// Make a custom greeter that uses the greeting "Welcome".
145-
let greeter = ctx.exports.Greeter({props: {greeting: "Welcome"}})
139+
async fetch(request, env, ctx) {
140+
// Make a custom greeter that uses the greeting "Welcome".
141+
let greeter = ctx.exports.Greeter({ props: { greeting: "Welcome" } });
146142

147-
// Greet the world. Returns "Welcome, World!"
148-
let greeting = await greeter.greet("World")
143+
// Greet the world. Returns "Welcome, World!"
144+
let greeting = await greeter.greet("World");
149145

150-
return new Response(greeting);
151-
}
146+
return new Response(greeting);
147+
},
152148
} satisfies ExportedHandler<Env>;
153149
```
154150

@@ -170,8 +166,16 @@ When declaring an entrypoint class that accepts `props`, make sure to declare it
170166

171167
`waitUntil` is commonly used to:
172168

173-
* Fire off events to external analytics providers. (note that when you use [Workers Analytics Engine](/analytics/analytics-engine/), you do not need to use `waitUntil`)
174-
* Put items into cache using the [Cache API](/workers/runtime-apis/cache/)
169+
- Fire off events to external analytics providers. (note that when you use [Workers Analytics Engine](/analytics/analytics-engine/), you do not need to use `waitUntil`)
170+
- Put items into cache using the [Cache API](/workers/runtime-apis/cache/)
171+
172+
:::caution[`waitUntil` has a 30-second time limit]
173+
174+
The Worker's lifetime is extended for up to 30 seconds after the response is sent or the client disconnects. This time limit is shared across all `waitUntil()` calls within the same request — if any Promises have not settled after 30 seconds, they are cancelled. When `waitUntil` tasks are cancelled, the following warning will be logged to [Workers Logs](/workers/observability/logs/workers-logs/) and any attached [Tail Workers](/workers/observability/logs/tail-workers/): `waitUntil() tasks did not complete within the allowed time after invocation end and have been cancelled.`
175+
176+
If you need to guarantee that work completes successfully, you should send messages to a [Queue](/queues/) and process them in a separate consumer Worker. Queues provide reliable delivery and automatic retries, ensuring your work is not lost.
177+
178+
:::
175179

176180
:::note[Alternatives to waitUntil]
177181

@@ -186,21 +190,21 @@ For example:
186190

187191
```js
188192
export default {
189-
async fetch(request, env, ctx) {
190-
// Forward / proxy original request
191-
let res = await fetch(request);
193+
async fetch(request, env, ctx) {
194+
// Forward / proxy original request
195+
let res = await fetch(request);
192196

193-
// Add custom header(s)
194-
res = new Response(res.body, res);
195-
res.headers.set('x-foo', 'bar');
197+
// Add custom header(s)
198+
res = new Response(res.body, res);
199+
res.headers.set("x-foo", "bar");
196200

197-
// Cache the response
198-
// NOTE: Does NOT block / wait
199-
ctx.waitUntil(caches.default.put(request, res.clone()));
201+
// Cache the response
202+
// NOTE: Does NOT block / wait
203+
ctx.waitUntil(caches.default.put(request, res.clone()));
200204

201-
// Done
202-
return res;
203-
},
205+
// Done
206+
return res;
207+
},
204208
};
205209
```
206210

@@ -217,10 +221,10 @@ The `passThroughOnException` method allows a Worker to [fail open](https://commu
217221

218222
```js
219223
export default {
220-
async fetch(request, env, ctx) {
221-
// Proxy to origin on unhandled/uncaught exceptions
222-
ctx.passThroughOnException();
223-
throw new Error('Oops');
224-
},
224+
async fetch(request, env, ctx) {
225+
// Proxy to origin on unhandled/uncaught exceptions
226+
ctx.passThroughOnException();
227+
throw new Error("Oops");
228+
},
225229
};
226230
```

0 commit comments

Comments
 (0)