I've found a tractable way to implement these features in the language I'm currently working on, Wat.
My approach is to totally separate first-order control from higher-order control.
There is a set of Common Lisp-like first-order forms:
- block and return-from that establish and invoke a lexically-scoped one-shot escape continuation, respectively.
- unwind-protect aka "finally". Notably, unwind-protect is only sensitive to return-from, not to aborts via higher-order control.
These forms are implemented natively using JS try/catch and finally.
In addition there's a completely separate set of higher-order control forms from A Monadic Framework for Delimited Continuations.
Delimited continuations are implemented using a technique similar to Exceptional Continuations: ordinary code paths run on the normal JS stack; when a continuation is captured, the stack is unwound frame by frame up to the prompt, and at each frame, a resumption is added to the continuation that is built up during the unwinding. This technique is ten times faster than a naive scheme with heap-allocated stack frames, but currently doesn't support TCO.
First-order control is used for quotidian control flow, whereas higher-order control is used for heavy control flow lifting, such as making a REPL written in direct style work in the browser's asynchronous environment.
This is a quite intuitive model: in the small, one has the usual Common Lisp control flow, including restartable exceptions, whereas in the large, behind the scenes, control flow may be arbitrarily abstracted and composed with the higher-order control forms.