What does `Proxy.revocable(target, handler)` return in JavaScript, and what happens to the proxy once `revoke()` has been called?
answer
- not a constructor, a static method
- two things come back, one disables the other
- every later operation fails loudly
- one-way, and it drops the target
basics
~10 sProxy.revocable returns a plain object with two properties, proxy and revoke. Calling revoke() permanently disables the proxy: every operation that would reach a trap throws a TypeError, and the target and handler are released.
solid answer
~50 s`Proxy.revocable(target, handler)` is a static method that returns an ordinary object `{ proxy, revoke }` — you destructure it rather than using `new`. The `proxy` behaves exactly like one built with `new Proxy`; `revoke` is a zero-argument function that switches it off. After `revoke()`, any operation that would go through a trap — reading or writing a property, `in`, `delete`, key enumeration, calling it — throws a `TypeError`, regardless of what the handler said. The change is one-way: there is no un-revoke, and calling `revoke()` again is a harmless no-op. Revocation also clears the proxy's internal references to both target and handler, so a live proxy no longer keeps them from being garbage-collected. The point is to hand another party a reference you can withdraw later, and have their next use fail loudly rather than silently keep working.
code
javascript · 17 linesconst secret = { token: 'abc' };
const { proxy, revoke } = Proxy.revocable(secret, {});
function handOut() { return proxy; }
const borrowed = handOut();
console.log(borrowed.token); // 'abc'
revoke();
try {
borrowed.token;
} catch (e) {
console.log(e instanceof TypeError); // true
}
console.log(borrowed === proxy); // true — identity still works
revoke(); // second call: no-opgo deeper
Remember that Proxy.revocable is called without new and returns { proxy, revoke }, and that after revoke() using the proxy throws.
Be able to say precisely what throws — every trap-reaching operation, with a TypeError — and what does not: identity comparison and typeof. Know that revocation is one-way and that a repeat call is a no-op.
Explain the memory consequence: the proxy stops referencing target and handler, so a target held only through a handed-out proxy becomes collectable. Be ready to say why this beats a hand-rolled disposed flag.
Frame it as capability withdrawal at a trust boundary and name its limits honestly — values already extracted are gone for good, and a revocation mid-use surfaces as an exception in someone else's code, so the lifecycle has to be agreed, not just enforced.
## The shape of the API `Proxy.revocable` is a static method on the `Proxy` constructor, not a constructor itself — you call it plainly and destructure the result: ```js const { proxy, revoke } = Proxy.revocable({ secret: 42 }, {}); proxy.secret; // 42 revoke(); proxy.secret; // TypeError: Cannot perform 'get' on a proxy that has been revoked ``` The returned object is an ordinary object with exactly two own properties. `proxy` is a full proxy in every respect — same traps, same invariants, same defaults for missing traps. `revoke` is a function of no arguments that returns `undefined`. Note that the two are separable, and that separation is the whole design. You can pass the proxy to somebody else while keeping `revoke` to yourself; the recipient has no way to get at the revoke function from the proxy, and no way to reach the target through it either. ## What revocation does Internally, a proxy holds two references: the target and the handler. Revoking sets both to null and marks the object revoked. From then on, every internal method of that proxy throws a `TypeError` rather than consulting anything: ```js const { proxy, revoke } = Proxy.revocable({ a: 1 }, {}); revoke(); proxy.a; // TypeError proxy.a = 2; // TypeError 'a' in proxy; // TypeError delete proxy.a; // TypeError Object.keys(proxy); // TypeError JSON.stringify(proxy); // TypeError ``` The failures are loud and immediate, which is the point: a revoked capability should not degrade into an object that quietly returns `undefined`. A few things still work, because they are not object operations that reach an internal method. Identity comparison is unaffected — `proxy === proxy` is still `true`, and the value can still be stored in a `Map` or a `Set` as a key. `typeof` also still answers, reporting the kind fixed at construction time, because callability is decided when the proxy is created and not by asking the target. So a revoked proxy is a valid JavaScript value that simply refuses to be used as an object. ## One-way and idempotent There is no un-revoke and no flag to test with; the only way to know is to try an operation and catch the `TypeError`, or to track the state yourself alongside the revoke function. Calling `revoke()` a second time does nothing and does not throw. Because the revoke function is created together with the proxy and closes over it, keeping the revoke function alive keeps the proxy object alive too — but not the target, which was released at revocation. ## The memory angle That last detail is worth stating explicitly, because it is the practical difference between revoking and simply dropping your own reference. While a proxy is live it strongly references its target; anyone holding the proxy transitively holds the target. Revoking severs that link, so if the proxy was the last thing referring to the target, the target becomes collectable even though the recipient is still holding the proxy object. Dropping your own reference to the proxy would not have achieved that, because the recipient's reference keeps the whole chain alive. ## What it is for The motivating pattern is a withdrawable reference. You give a plug-in, a worker, an iframe-hosted script or a request handler a proxy instead of the real object; when their scope is over — the request finished, the plug-in unloaded, the session ended — you revoke, and every later use fails at the point of use with a clear error. That is strictly better than handing out the real object and hoping nobody keeps a copy, and better than a hand-rolled `if (disposed) throw` guard, which only covers the methods you remembered to guard. The caveats are worth stating too. Revocation applies to the proxy object, not to anything already extracted through it: if the holder read `proxy.config` into a local variable before you revoked, they still have that value, and if that value was an object they still have the object. A proxy is a gate on a reference, not a retroactive undo. And because every use after revocation throws, the holder's error handling has to be prepared for it — revoking a reference somebody is using in the middle of a loop turns into an exception in their code, not a graceful stop.
- Is there a way to test whether a proxy has been revoked without triggering an error?No — the language exposes no predicate and no observable flag. Any operation that would reach a trap throws, so the only checks are a `try`/`catch` around a harmless operation, or tracking the state yourself in the code that owns the revoke function. In practice you keep a boolean next to `revoke` rather than probing the proxy.
- How does revoking differ from just dropping your own reference to the proxy?Dropping your reference changes nothing for anyone else: the holder's reference keeps both the proxy and, through it, the target alive and fully usable. Revoking acts on the object itself, so every holder loses access at once and the internal link to the target is cut, letting the target be collected while the holder still has the now-inert proxy.
- Does revoking a proxy protect data the holder already read through it?No. Revocation is a gate on future operations only. Anything already extracted — a primitive copied into a variable, or worse a nested object reference obtained through a `get` — remains in the holder's hands untouched. If that matters, the handler has to avoid returning raw inner objects in the first place, typically by wrapping them in proxies of their own.
saying these in an interview costs you the question
- Calls it with new, as if it were a constructor
- Thinks revoked operations return undefined instead of throwing
- Believes revocation can be undone or toggled back on
- Assumes revoking invalidates values already read through the proxy
- Says the second revoke() call throws an error