Skip to content

Commit ee616b7

Browse files
committed
Update SCOPING.md
1 parent edde9b6 commit ee616b7

File tree

2 files changed

+217
-0
lines changed

2 files changed

+217
-0
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,9 @@ const asyncLocal = new AsyncLocal();
202202
})();
203203
```
204204
205+
> Note: There are controversial thought on the dynamic scoping and AsyncLocal,
206+
> checkout [SCOPING.md][] for more details.
207+
205208
The optional `valueChangedListener` will be called each time the value in the
206209
current async flow has been updated by explicit `AsyncLocal.setValue` call. It
207210
can be treated as a property setter of an object.
@@ -546,3 +549,4 @@ resources tracking for APM vendors. On which Node.js also implemented
546549
[`AsyncResource.runInAsyncScope`]: https://nodejs.org/dist/latest-v14.x/docs/api/async_hooks.html#async_hooks_asyncresource_runinasyncscope_fn_thisarg_args
547550
[Domain Module Postmortem]: https://nodejs.org/en/docs/guides/domain-postmortem/
548551
[SOLUTION.md]: ./SOLUTION.md
552+
[SCOPING.md]: ./SCOPING.md

SCOPING.md

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
# Scoping of AsyncLocal
2+
3+
The major concerns of AsyncLocal advancing to Stage 1 of TC39 proposal process
4+
is that there are potential dynamic scoping of the semantics of AsyncLocal.
5+
This document is about defining the scoping of AsyncLocal.
6+
7+
### Dynamic Scoping
8+
9+
A classic dynamic scoping issue is: the name `x` in `g` will be determined by
10+
the callee of `g`. If `g` is called at root scope, the name `x` refers to the
11+
one defined in the root scope. If `g` is called in `f`, the name `x` refers to
12+
the one defined in the scope of `f`.
13+
14+
```js
15+
$ # bash language
16+
$ x=1
17+
$ function g () { echo $x ; x=2 ; }
18+
$ function f () { local x=3 ; g ; }
19+
$ f # does this print 1, or 3?
20+
3
21+
$ echo $x # does this print 1, or 2?
22+
1
23+
```
24+
25+
However, the naming scope of async local is identical to a regular variable in
26+
JavaScript. Since JavaScript variables are lexically scoped, the naming of
27+
async local instances are lexically scoped too. It is not possible to access
28+
an async local that are not explicitly referenced.
29+
30+
```js
31+
const asyncLocal = new AsyncLocal();
32+
asyncLocal.setValue(1);
33+
34+
function g() {
35+
console.log(asyncLocal.getValue(); // print 1;
36+
asyncLocal.setValue(2);
37+
}
38+
function f() {
39+
const asyncLocal = new AsyncLocal();
40+
asyncLocal.setValue(3);
41+
g();
42+
}
43+
f();
44+
45+
console.log(asyncLocal.getValue()); // print 2;
46+
```
47+
48+
Hence, referencing the names of AsyncLocal instances have the same meaning with
49+
regular variables in lexically scoped closures.
50+
51+
```js
52+
const asyncLocal = new AsyncLocal();
53+
asyncLocal.setValue(1);
54+
55+
function f() {
56+
const asyncLocal = new AsyncLocal();
57+
asyncLocal.setValue(3);
58+
g();
59+
function g() {
60+
console.log(asyncLocal.getValue(); // print 3;
61+
asyncLocal.setValue(2);
62+
}
63+
}
64+
f();
65+
66+
console.log(asyncLocal.getValue()); // print 1;
67+
```
68+
69+
### Dynamic Scoping: dependency on callee
70+
71+
One argument on the dynamic scoping is that the values in AsyncLocal can be
72+
changed depending on which the callee is.
73+
74+
However, the definition of whether the value of an async local can be changed
75+
has the same meaning with a regular JavaScript variable: the JavaScript
76+
variables changes all the time, even though it is encapsulated by the closure.
77+
78+
```js
79+
const asyncLocal = new AsyncLocal();
80+
let local;
81+
82+
function g() {
83+
console.log('asyncLocal:', asyncLocal.getValue();
84+
console.log('local:', local);
85+
}
86+
function f() {
87+
const asyncLocal = new AsyncLocal();
88+
let local;
89+
asyncLocal.setValue(3);
90+
local = 3;
91+
g();
92+
}
93+
94+
asyncLocal.setValue(1);
95+
local = 1;
96+
f();
97+
// => asyncLocal: 1;
98+
// => local: 1;
99+
asyncLocal.setValue(2);
100+
local = 2;
101+
f();
102+
// => asyncLocal: 2;
103+
// => local: 2;
104+
```
105+
106+
### Dynamic Scoping: contexts
107+
108+
Can a closure capture the value of an async local that is relevant at a given
109+
moment in time? As the value of async local is single-directed propagated,
110+
value set in subsequent async callback will not be feed back to its original
111+
context.
112+
113+
```js
114+
const asyncLocal = new AsyncLocal();
115+
let local;
116+
117+
(function main() {
118+
asyncLocal.setValue('1');
119+
local = '1';
120+
121+
// (1)
122+
setTimeout(() => {
123+
console.log(asyncLocal.getValue()); // '1' is propagated.
124+
asyncLocal.setValue('2');
125+
setTimeout(() => {
126+
console.log(asyncLocal.getValue()); // '2' is propagated.
127+
}, 1000);
128+
}, 1000);
129+
130+
// (2)
131+
setTimeout(() => {
132+
console.log(asyncLocal.getValue()); // '1' is propagated.
133+
asyncLocal.setValue('3');
134+
setTimeout(() => {
135+
console.log(asyncLocal.getValue()); // '3' is propagated.
136+
}, 1000);
137+
}, 1000);
138+
})();
139+
```
140+
141+
Even though the value of async local depends on in which context the async
142+
flow is execution, the naming scope of async local is clear and identical to
143+
regular JavaScript variables. The difference between regular JavaScript
144+
variables and async locals is the value in the variable slot.
145+
146+
```js
147+
const asyncLocal = new AsyncLocal();
148+
let local;
149+
150+
function g() {
151+
console.log('asyncLocal:', asyncLocal.getValue();
152+
console.log('local:', local);
153+
}
154+
function f() {
155+
const asyncLocal = new AsyncLocal();
156+
let local;
157+
asyncLocal.setValue(3);
158+
local = 3;
159+
g();
160+
}
161+
162+
asyncLocal.setValue(1);
163+
local = 1;
164+
setTimeout(f, 1);
165+
// => asyncLocal: 1;
166+
// value is propagated to the async flow.
167+
// => local: 2;
168+
// as following two line were evaluated before both run of `g`.
169+
asyncLocal.setValue(2);
170+
local = 2;
171+
setTimeout(f, 1);
172+
// => asyncLocal: 2;
173+
// value is propagated to the async flow.
174+
// => local: 2;
175+
// as above two set were evaluated before both run of `g`.
176+
```
177+
178+
Additional, the async locals behave identically to lexically scoped variables in
179+
synchronous executions.
180+
181+
```js
182+
const asyncLocal = new AsyncLocal();
183+
let local;
184+
185+
(function main() {
186+
asyncLocal.setValue('1');
187+
local = '1';
188+
189+
// (1)
190+
(function () => {
191+
console.log(asyncLocal.getValue()); // => '1'
192+
console.log(local); // => '1'
193+
asyncLocal.setValue('2');
194+
local = '2';
195+
(function () => {
196+
console.log(asyncLocal.getValue()); // => '2'
197+
console.log(local); // => '2'
198+
})();
199+
})();
200+
201+
// (2)
202+
(function () => {
203+
console.log(asyncLocal.getValue()); // => '2'
204+
console.log(local); // => '2'
205+
asyncLocal.setValue('3');
206+
local = '3';
207+
(function () => {
208+
console.log(asyncLocal.getValue()); // => '3'
209+
console.log(local); // => '3'
210+
})();
211+
})();
212+
})();
213+
```

0 commit comments

Comments
 (0)