Skip to content

Commit e5fa877

Browse files
authored
Merge pull request #331 from cloudflare/jsnell/asynccontext-additional-tweaks
2 parents e497feb + dbf9bc6 commit e5fa877

File tree

4 files changed

+56
-1
lines changed

4 files changed

+56
-1
lines changed

src/workerd/api/node/async-hooks.c++

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,18 @@ v8::Local<v8::Value> AsyncLocalStorage::getStore(jsg::Lock& js) {
5757
return v8::Undefined(js.v8Isolate);
5858
}
5959

60+
v8::Local<v8::Function> AsyncLocalStorage::bind(jsg::Lock& js, v8::Local<v8::Function> fn) {
61+
KJ_IF_MAYBE(frame, jsg::AsyncContextFrame::current(js)) {
62+
return frame->wrap(js, fn);
63+
} else {
64+
return jsg::AsyncContextFrame::wrapRoot(js, fn);
65+
}
66+
}
67+
68+
v8::Local<v8::Function> AsyncLocalStorage::snapshot(jsg::Lock& js) {
69+
return jsg::AsyncContextFrame::wrapSnapshot(js);
70+
}
71+
6072
namespace {
6173
kj::Maybe<jsg::Ref<jsg::AsyncContextFrame>> tryGetFrameRef(jsg::Lock& js) {
6274
return jsg::AsyncContextFrame::current(js).map(

src/workerd/api/node/async-hooks.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,15 @@ class AsyncLocalStorage final: public jsg::Object {
4646

4747
v8::Local<v8::Value> getStore(jsg::Lock& js);
4848

49+
static v8::Local<v8::Function> bind(jsg::Lock& js, v8::Local<v8::Function> fn);
50+
// Binds the given function to the current async context frame such that
51+
// whenever the function is called, the bound frame is entered.
52+
53+
static v8::Local<v8::Function> snapshot(jsg::Lock& js);
54+
// Returns a function bound to the current async context frame that calls
55+
// the function passed to it as the only argument within that frame.
56+
// Equivalent to AsyncLocalStorage.bind((cb, ...args) => cb(...args)).
57+
4958
inline void enterWith(jsg::Lock&, v8::Local<v8::Value>) {
5059
KJ_UNIMPLEMENTED("asyncLocalStorage.enterWith() is not implemented");
5160
}
@@ -60,6 +69,8 @@ class AsyncLocalStorage final: public jsg::Object {
6069
JSG_METHOD(getStore);
6170
JSG_METHOD(enterWith);
6271
JSG_METHOD(disable);
72+
JSG_STATIC_METHOD(bind);
73+
JSG_STATIC_METHOD(snapshot);
6374

6475
if (flags.getNodeJsCompat()) {
6576
JSG_TS_OVERRIDE(AsyncLocalStorage<T> {
@@ -68,6 +79,8 @@ class AsyncLocalStorage final: public jsg::Object {
6879
exit<R, TArgs extends any[]>(callback: (...args: TArgs) => R, ...args: TArgs): R;
6980
disable(): void;
7081
enterWith(store: T): void;
82+
static bind<Func extends (...args: any[]) => any>(fn: Func): Func;
83+
static snapshot<R, TArgs extends any[]>() : ((...args: TArgs) => R, ...args: TArgs) => R;
7184
});
7285
} else {
7386
JSG_TS_OVERRIDE(type AsyncLocalStorage = never);
@@ -80,6 +93,10 @@ class AsyncLocalStorage final: public jsg::Object {
8093

8194

8295
class AsyncResource final: public jsg::Object {
96+
// Note: The AsyncResource class is provided for Node.js backwards compatibility.
97+
// The class can be replaced entirely for async context tracking using the
98+
// AsyncLocalStorage.bind() and AsyncLocalStorage.snapshot() APIs.
99+
//
83100
// The AsyncResource class is an object that user code can use to define its own
84101
// async resources for the purpose of storage context propagation. For instance,
85102
// let's imagine that we have an EventTarget and we want to register two event listeners

src/workerd/jsg/async-context.c++

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,28 @@ v8::Local<v8::Function> AsyncContextFrame::wrap(
6969
return wrap(js, fn.getHandle(js), thisArg);
7070
}
7171

72+
v8::Local<v8::Function> AsyncContextFrame::wrapSnapshot(Lock& js) {
73+
auto isolate = js.v8Isolate;
74+
auto context = isolate->GetCurrentContext();
75+
76+
return js.wrapReturningFunction(context, JSG_VISITABLE_LAMBDA(
77+
(frame = AsyncContextFrame::currentRef(js)),
78+
(frame),
79+
(Lock& js, const v8::FunctionCallbackInfo<v8::Value>& args) {
80+
auto context = js.v8Isolate->GetCurrentContext();
81+
JSG_REQUIRE(args[0]->IsFunction(), TypeError, "The first argument must be a function");
82+
auto fn = args[0].As<v8::Function>();
83+
kj::Vector<v8::Local<v8::Value>> argv(args.Length() - 1);
84+
for (int n = 1; n < args.Length(); n++) {
85+
argv.add(args[n]);
86+
}
87+
88+
AsyncContextFrame::Scope scope(js, frame);
89+
return check(fn->Call(context, context->Global(), argv.size(), argv.begin()));
90+
}
91+
));
92+
}
93+
7294
v8::Local<v8::Function> AsyncContextFrame::wrap(
7395
Lock& js,
7496
v8::Local<v8::Function> fn,
@@ -93,7 +115,6 @@ v8::Local<v8::Function> AsyncContextFrame::wrap(
93115
}
94116

95117
AsyncContextFrame::Scope scope(js, *frame.get());
96-
v8::Local<v8::Value> result;
97118
return check(function->Call(context, thisArg.getHandle(js), args.Length(), argv.begin()));
98119
}));
99120
}

src/workerd/jsg/async-context.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,11 @@ class AsyncContextFrame final: public Wrappable {
125125
// Wraps the given JavaScript function such that whenever the wrapper function is called,
126126
// the root AsyncContextFrame will be entered.
127127

128+
static v8::Local<v8::Function> wrapSnapshot(Lock& js);
129+
// Returns a function that captures the current frame and calls the function passed
130+
// in as an argument within that captured context. Equivalent to wrapping a function
131+
// with the signature (cb, ...args) => cb(...args).
132+
128133
v8::Local<v8::Function> wrap(
129134
Lock& js, V8Ref<v8::Function>& fn,
130135
kj::Maybe<v8::Local<v8::Value>> thisArg = nullptr);

0 commit comments

Comments
 (0)