Remote-procedure call (RPC)·续
await counter.increment(2) # returns 2 await counter.increment(1) # returns 3 await counter.increment(-5) # returns -2
count = await counter.value # returns -2
return Response(str(count))
Note
Refer to [Explicit Resource Management](https://developers.cloudflare.com/workers/runtime-apis/rpc/lifecycle) to learn more about the `using` declaration shown in the JavaScript/TypeScript example above.
Classes that extend `RpcTarget` work a lot like functions: the object itself is not serialized, but is instead replaced by a stub. In this case, the stub itself is not callable, but its methods are. Calling any method on the stub actually makes an RPC back to the original object, where it was created.
As shown above, you can also access properties of classes. Properties behave like RPC methods that don't take any arguments — you await the property to asynchronously fetch its current value. Note that the act of awaiting the property (which, behind the scenes, calls `.then()` on it) is what causes the property to be fetched. If you do not use `await` when accessing the property, it will not be fetched.
Note
While it's possible to define a similar interface to the caller using an object that contains many functions, this is less efficient. If you return an object that contains five functions, then you are creating five stubs. If you return a class instance, where the class declares five methods, you are only returning a single stub. Returning a single stub is often more efficient and easier to reason about. Moreover, when returning a plain object (not a class), non-function properties of the object will be transmitted at the time the object itself is transmitted; they cannot be fetched asynchronously on-demand.
Note
Classes which do not inherit `RpcTarget` cannot be sent over RPC at all. This differs from Structured Clone, which defines application-defined classes as cloneable. Why the difference? By default, the Structured Clone algorithm simply ignores an object's class entirely. So, the recipient receives a plain object, containing the original object's instance properties but entirely missing its original type. This behavior is rarely useful in practice, and could be confusing if the developer had intended the class to be treated as an `RpcTarget`. So, Workers RPC has chosen to disallow classes that are not `RpcTarget`s, to avoid any confusion.
### Promise pipelining
When you call an RPC method and get back an object, it's common to immediately call a method on the object:
```js
// Two round trips.
using counter = await env.COUNTER_SERVICE.getCounter();
await counter.increment();
// Two round trips.
using counter = await env.COUNTER_SERVICE.getCounter();
await counter.increment();
# Two round trips.
counter = await self.env.COUNTER_SERVICE.get_counter()
await counter.increment()
But consider the case where the Worker service that you are calling may be far away across the network, as in the case of Smart Placement or Durable Objects. The code above makes two round trips, once when calling getCounter(), and again when calling .increment(). We'd like to avoid this.
With most RPC systems, the only way to avoid the problem would be to combine the two calls into a single "batch" call, perhaps called getCounterAndIncrement(). However, this makes the interface worse. You wouldn't design a local interface this way.
Workers RPC allows a different approach: You can simply omit the first await:
// Only one round trip! Note the missing `await`.
using promiseForCounter = env.COUNTER_SERVICE.getCounter();
await promiseForCounter.increment();
// Only one round trip! Note the missing `await`.
using promiseForCounter = env.COUNTER_SERVICE.getCounter();
await promiseForCounter.increment();
# Only one round trip! Note the missing await.
promise_for_counter = self.env.COUNTER_SERVICE.get_counter()
await promise_for_counter.increment()
In this code, getCounter() returns a promise for a counter. Normally, the only thing you would do with a promise is await it. However, Workers RPC promises are special: they also allow you to initiate speculative calls on the future result of the promise. These calls are sent to the server immediately, without waiting for the initial call to complete. Thus, multiple chained calls can be completed in a single round trip.
How does this work? The promise returned by an RPC is not a real JavaScript Promise. Instead, it is a custom "Thenable" ↗︎. It has a .then() method like Promise, which allows it to be used in all the places where you'd use a normal Promise. For instance, you can await it. But, in addition to that, an RPC promise also acts like a stub. Calling any method name on the promise forms a speculative call on the promise's eventual result. This is known as "promise pipelining".
This works when calling properties of objects returned by RPC methods as well. For example:
import { WorkerEntrypoint } from "cloudflare:workers";
export class MyService extends WorkerEntrypoint {
async foo() {
return {
bar: {
baz: () => "qux",
},
};
}
}
import { WorkerEntrypoint } from "cloudflare:workers";
export class MyService extends WorkerEntrypoint {
async foo() {
return {
bar: {
baz: () => "qux",
},
};
}
}
from workers import WorkerEntrypoint
class MyService(WorkerEntrypoint):
async def foo(self):
return {
"bar": {
"baz": lambda: "qux",
},
}
export default {
async fetch(request, env) {
using foo = env.MY_SERVICE.foo();
let baz = await foo.bar.baz();
return new Response(baz);
},
};
export default {
async fetch(request, env) {
using foo = env.MY_SERVICE.foo();
let baz = await foo.bar.baz();
return new Response(baz);
},
};
from workers import WorkerEntrypoint, Response
class Default(WorkerEntrypoint):
async def fetch(self, request):
foo = self.env.MY_SERVICE.foo()
baz = await foo.bar.baz()
return Response(baz)
If the initial RPC ends up throwing an exception, then any pipelined calls will also fail with the same exception
ReadableStream, WritableStream, Request and Response
You can send and receive ReadableStream, WritableStream, Request, and Response using RPC methods. When doing so, bytes in the body are automatically streamed with appropriate flow control. This allows you to send messages over RPC which are larger than the typical 32 MiB limit.
Only byte-oriented streams ↗︎ (streams with an underlying byte source of type: "bytes") are supported.
In all cases, ownership of the stream is transferred to the recipient. The sender can no longer read/write the stream after sending it. If the sender wishes to keep its own copy, it can use the tee() method of ReadableStream ↗︎ or the clone() method of Request or Response ↗︎. Keep in mind that doing this may force the system to buffer bytes and lose the benefits of flow control.
Forwarding RPC stubs
A stub received over RPC from one Worker can be forwarded over RPC to another Worker.
using counter = env.COUNTER_SERVICE.getCounter();
await env.ANOTHER_SERVICE.useCounter(counter);
using counter = env.COUNTER_SERVICE.getCounter();
await env.ANOTHER_SERVICE.useCounter(counter);
counter = self.env.COUNTER_SERVICE.get_counter()
await self.env.ANOTHER_SERVICE.use_counter(counter)
Here, three different workers are involved:
- The calling Worker (we'll call this the "introducer")
COUNTER_SERVICEANOTHER_SERVICE
When ANOTHER_SERVICE calls a method on the counter that is passed to it, this call will automatically be proxied through the introducer and on to the RpcTarget class implemented by COUNTER_SERVICE.
In this way, the introducer Worker can connect two Workers that did not otherwise have any ability to form direct connections to each other.
Currently, this proxying only lasts until the end of the Workers' execution contexts. A proxy connection cannot be persisted for later use.
Video Tutorial
In this video, we explore how Cloudflare Workers support Remote Procedure Calls (RPC) to simplify communication between Workers. Learn how to implement RPC in your JavaScript applications and build serverless solutions with ease. Whether you're managing microservices or optimizing web architecture, this tutorial will show you how to quickly set up and use Cloudflare Workers for RPC calls. By the end of this video, you'll understand how to call functions between Workers, pass functions as arguments, and implement user authentication with Cloudflare Workers.
More Details
Limitations
- Smart Placement is currently ignored when making RPC calls. If Smart Placement is enabled for Worker A, and Worker B declares a Service Binding to it, when Worker B calls Worker A via RPC, Worker A will run locally, on the same machine.
- The maximum serialized RPC limit is 32 MiB. Consider using
ReadableStreamwhen returning more data.
export class MyService extends WorkerEntrypoint {
async foo() {
// Although this works, it puts a lot of memory pressure on the isolate.
// If possible, streaming the data from its original source is much preferred and would yield better performance.
// If you must buffer the data into memory, consider chunking it into smaller pieces if possible.
const sizeInBytes = 33 * 1024 * 1024; // 33 MiB
const arr = new Uint8Array(sizeInBytes);
return new ReadableStream({
start(controller) {
controller.enqueue(arr);
controller.close();
},
});
}
}
```
```ts
export class MyService extends WorkerEntrypoint {
async foo() {
// Although this works, it puts a lot of memory pressure on the isolate.
// If possible, streaming the data from its original source is much preferred and would yield better performance.
// If you must buffer the data into memory, consider chunking it into smaller pieces if possible.
const sizeInBytes = 33 * 1024 * 1024; // 33 MiB
const arr = new Uint8Array(sizeInBytes);
return new ReadableStream({
start(controller) {
controller.enqueue(arr);
controller.close();
},
});
}
}
```
Was this helpful?
YesNo
## On this page
[Docs](https://developers.cloudflare.com/)
```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/workers/runtime-apis/rpc/#page","headline":"Remote-procedure call (RPC)","description":"The built-in, JavaScript-native RPC system built into Workers and Durable Objects.","url":"https://developers.cloudflare.com/workers/runtime-apis/rpc/","inLanguage":"en","image":"https://developers.cloudflare.com/workers/runtime-apis/rpc/og.png?v=900a62b1263ba25a","dateModified":"2026-10-12","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"},"keywords":["RPC"]}
description: How exceptions, error properties, and stack traces work with the Workers RPC system. title: Error handling image: https://developers.cloudflare.com/workers/runtime-apis/rpc/error-handling/og.png?v=46c93420c6e7f538
Documentation Index
Fetch the complete documentation index at: https://developers.cloudflare.com/workers/llms.txt
Use this file to discover all available pages before exploring further.
