Skip to content

Commit b243400

Browse files
jridgewellandreubotellaljharb
authored
Handle FinalizationRegistry (tc39#69)
* Handle FinalizationRegistry * Fix delayed reactions * Add `[[AsyncContextSnapshot]]` to PromiseReaction * Update spec.html Co-authored-by: Andreu Botella <[email protected]> * Update spec.html Co-authored-by: Jordan Harband <[email protected]> * Use creation-time context * Small fixes * Unpin * Mark change --------- Co-authored-by: Andreu Botella <[email protected]> Co-authored-by: Jordan Harband <[email protected]>
1 parent f9cc2ac commit b243400

File tree

3 files changed

+216
-92
lines changed

3 files changed

+216
-92
lines changed

package-lock.json

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
"devDependencies": {
2727
"@esbuild-kit/cjs-loader": "2.4.1",
2828
"@esbuild-kit/esm-loader": "2.5.4",
29-
"@tc39/ecma262-biblio": "^2.1.2517",
29+
"@tc39/ecma262-biblio": "^2.1.2674",
3030
"@types/mocha": "10.0.1",
3131
"@types/node": "18.11.18",
3232
"ecmarkup": "^18.2.0",

spec.html

Lines changed: 208 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -129,86 +129,28 @@ <h1>Agents</h1>
129129
</emu-table>
130130
</emu-clause>
131131

132-
<emu-clause id="sec-jobs">
133-
<h1>Jobs and Host Operations to Enqueue Jobs</h1>
134-
135-
<emu-clause id="sec-jobcallback-records">
136-
<h1>JobCallback Records</h1>
137-
<p>This proposal adds a new field to the JobCallback Record as the following table:</p>
138-
<emu-table id="table-jobcallback-records" caption="JobCallback Record Fields">
139-
<table>
140-
<tr>
141-
<th>
142-
Field Name
143-
</th>
144-
<th>
145-
Value
146-
</th>
147-
<th>
148-
Meaning
149-
</th>
150-
</tr>
151-
<tr>
152-
<td>
153-
[[Callback]]
154-
</td>
155-
<td>
156-
a function object
157-
</td>
158-
<td>
159-
The function to invoke when the Job is invoked.
160-
</td>
161-
</tr>
162-
<tr>
163-
<td>
164-
<ins>[[AsyncContextSnapshot]]</ins>
165-
</td>
166-
<td>
167-
<ins>a List of Async Context Mapping Records</ins>
168-
</td>
169-
<td>
170-
<ins>A map from the AsyncContext.Variable instances to the saved ECMAScript language value. Every Record in the List contains a unique [[AsyncContextKey]].</ins>
171-
</td>
172-
</tr>
173-
<tr>
174-
<td>
175-
[[HostDefined]]
176-
</td>
177-
<td>
178-
anything (default value is ~empty~)
179-
</td>
180-
<td>
181-
Field reserved for use by hosts.
182-
</td>
183-
</tr>
184-
</table>
185-
</emu-table>
186-
</emu-clause>
187-
188-
<emu-clause id="sec-hostmakejobcallback" type="host-defined abstract operation">
189-
<h1>
190-
HostMakeJobCallback (
191-
_callback_: a function object,
192-
): a JobCallback Record
193-
</h1>
194-
<dl class="header">
195-
</dl>
196-
<p>An implementation of HostMakeJobCallback must conform to the following requirements:</p>
197-
<ul>
198-
<li>It must return a JobCallback Record whose [[Callback]] field is _callback_,</li>
199-
<li><ins>It must return a JobCallback Record whose [[AsyncContextSnapshot]] field is the result of performing AsyncContextSnapshot().</ins></li>
200-
</ul>
201-
<p>The default implementation of HostMakeJobCallback performs the following steps when called:</p>
202-
<emu-alg>
203-
1. <del>Return the JobCallback Record { [[Callback]]: _callback_, [[HostDefined]]: ~empty~ }.</del>
204-
1. <ins>Let _snapshotMapping_ be AsyncContextSnapshot().</ins>
205-
1. <ins>Return the JobCallback Record { [[Callback]]: _callback_, [[AsyncContextSnapshot]]: _snapshotMapping_, [[HostDefined]]: ~empty~ }.</ins>
206-
</emu-alg>
207-
<p>ECMAScript hosts that are not web browsers must use the default implementation of HostMakeJobCallback.</p>
208-
<emu-note>
209-
<p>HostMakeJobCallback snapshots the current AsyncContext global state. The snapshot is restored before calling HostCallJobCallback in NewPromiseReactionJob and NewPromiseResolveThenableJob.</p>
210-
</emu-note>
211-
</emu-clause>
132+
<emu-clause id="sec-cleanup-finalization-registry" type="abstract operation">
133+
<h1>
134+
CleanupFinalizationRegistry (
135+
_finalizationRegistry_: a FinalizationRegistry,
136+
): either a normal completion containing ~unused~ or a throw completion
137+
</h1>
138+
<dl class="header">
139+
</dl>
140+
<emu-alg>
141+
1. <del>Assert: _finalizationRegistry_ has [[Cells]] and [[CleanupCallback]] internal slots.</del>
142+
1. <ins>Assert: _finalizationRegistry_ has [[Cells]], [[CleanupCallback]], and [[FinalizationRegistryAsyncContextSnapshot]] internal slots.</ins>
143+
1. Let _callback_ be _finalizationRegistry_.[[CleanupCallback]].
144+
1. While _finalizationRegistry_.[[Cells]] contains a Record _cell_ such that _cell_.[[WeakRefTarget]] is ~empty~, an implementation may perform the following steps:
145+
1. Choose any such _cell_.
146+
1. Remove _cell_ from _finalizationRegistry_.[[Cells]].
147+
1. <del>Perform ? HostCallJobCallback(_callback_, *undefined*, « _cell_.[[HeldValue]] »).</del>
148+
1. <ins>Let _previousContextMapping_ be AsyncContextSwap(_finalizationRegistry_.[[FinalizationRegistryAsyncContextSnapshot]]).</ins>
149+
1. <ins>Let _result_ be Completion(HostCallJobCallback(_callback_, *undefined*, « _cell_.[[HeldValue]] »)).</ins>
150+
1. <ins>AsyncContextSwap(_previousContextMapping_).</ins>
151+
1. <ins>Perform ? _result_.</ins>
152+
1. Return ~unused~.
153+
</emu-alg>
212154
</emu-clause>
213155
</emu-clause>
214156

@@ -273,6 +215,71 @@ <h1>Promise Objects</h1>
273215
<emu-clause id="sec-promise-abstract-operations">
274216
<h1>Promise Abstract Operations</h1>
275217

218+
<emu-clause id="sec-promisereaction-records">
219+
<h1>PromiseReaction Records</h1>
220+
<p>A <dfn variants="PromiseReaction Records">PromiseReaction Record</dfn> is a Record value used to store information about how a promise should react when it becomes resolved or rejected with a given value. PromiseReaction Records are created by the PerformPromiseThen abstract operation, and are used by the Abstract Closure returned by NewPromiseReactionJob.</p>
221+
<p>PromiseReaction Records have the fields listed in <emu-xref href="#table-promisereaction-record-fields"></emu-xref>.</p>
222+
<emu-table id="table-promisereaction-record-fields" caption="PromiseReaction Record Fields" oldids="table-58">
223+
<table>
224+
<tr>
225+
<th>
226+
Field Name
227+
</th>
228+
<th>
229+
Value
230+
</th>
231+
<th>
232+
Meaning
233+
</th>
234+
</tr>
235+
<tr>
236+
<td>
237+
[[Capability]]
238+
</td>
239+
<td>
240+
a PromiseCapability Record or *undefined*
241+
</td>
242+
<td>
243+
The capabilities of the promise for which this record provides a reaction handler.
244+
</td>
245+
</tr>
246+
<tr>
247+
<td>
248+
[[Type]]
249+
</td>
250+
<td>
251+
~fulfill~ or ~reject~
252+
</td>
253+
<td>
254+
The [[Type]] is used when [[Handler]] is ~empty~ to allow for behaviour specific to the settlement type.
255+
</td>
256+
</tr>
257+
<tr>
258+
<td>
259+
[[Handler]]
260+
</td>
261+
<td>
262+
a JobCallback Record or ~empty~
263+
</td>
264+
<td>
265+
The function that should be applied to the incoming value, and whose return value will govern what happens to the derived promise. If [[Handler]] is ~empty~, a function that depends on the value of [[Type]] will be used instead.
266+
</td>
267+
</tr>
268+
<tr>
269+
<td>
270+
<ins>[[PromiseAsyncContextSnapshot]]</ins>
271+
</td>
272+
<td>
273+
<ins>a List of Async Context Mapping Records</ins>
274+
</td>
275+
<td>
276+
<ins>A map from the AsyncContext.Variable instances to the saved ECMAScript language value. Every Record in the List contains a unique [[AsyncContextKey]].</ins>
277+
</td>
278+
</tr>
279+
</table>
280+
</emu-table>
281+
</emu-clause>
282+
276283
<emu-clause id="sec-host-promise-rejection-tracker" type="host-defined abstract operation">
277284
<h1>
278285
HostPromiseRejectionTracker (
@@ -321,7 +328,7 @@ <h1>
321328
1. Let _promiseCapability_ be _reaction_.[[Capability]].
322329
1. Let _type_ be _reaction_.[[Type]].
323330
1. Let _handler_ be _reaction_.[[Handler]].
324-
1. <ins>Let _previousContextMapping_ be AsyncContextSwap(_handler_.[[AsyncContextSnapshot]]).</ins>
331+
1. <ins>Let _previousContextMapping_ be AsyncContextSwap(_reaction_.[[PromiseAsyncContextSnapshot]]).</ins>
325332
1. If _handler_ is ~empty~, then
326333
1. If _type_ is ~fulfill~, then
327334
1. let _handlerResult_ be NormalCompletion(_argument_).
@@ -364,9 +371,10 @@ <h1>
364371
<dl class="header">
365372
</dl>
366373
<emu-alg>
367-
1. Let _job_ be a new Job Abstract Closure with no parameters that captures _promiseToResolve_, _thenable_, and _then_ and performs the following steps when called:
374+
1. <ins>Let _snapshot_ be AsyncContextSnapshot().</ins>
375+
1. Let _job_ be a new Job Abstract Closure with no parameters that captures _promiseToResolve_, _thenable_, _then_, <ins>and _snapshot_</ins> and performs the following steps when called:
368376
1. Let _resolvingFunctions_ be CreateResolvingFunctions(_promiseToResolve_).
369-
1. <ins>Let _previousContextMapping_ be AsyncContextSwap(_then_.[[AsyncContextSnapshot]]).</ins>
377+
1. <ins>Let _previousContextMapping_ be AsyncContextSwap(_snapshot_).</ins>
370378
1. Let _thenCallResult_ be Completion(HostCallJobCallback(_then_, _thenable_, « _resolvingFunctions_.[[Resolve]], _resolvingFunctions_.[[Reject]] »)).
371379
1. If _thenCallResult_ is an abrupt completion, then
372380
1. <del>Return ? Call(_resolvingFunctions_.[[Reject]], *undefined*, « _thenCallResult_.[[Value]] »).</del>
@@ -386,6 +394,78 @@ <h1>
386394
</emu-note>
387395
</emu-clause>
388396
</emu-clause>
397+
398+
<emu-clause id="sec-properties-of-the-promise-prototype-object">
399+
<h1>Properties of the Promise Prototype Object</h1>
400+
<p>The <dfn>Promise prototype object</dfn>:</p>
401+
<ul>
402+
<li>is <dfn>%Promise.prototype%</dfn>.</li>
403+
<li>has a [[Prototype]] internal slot whose value is %Object.prototype%.</li>
404+
<li>is an ordinary object.</li>
405+
<li>does not have a [[PromiseState]] internal slot or any of the other internal slots of Promise instances.</li>
406+
</ul>
407+
408+
<emu-clause id="sec-promise.prototype.then">
409+
<h1>Promise.prototype.then ( _onFulfilled_, _onRejected_ )</h1>
410+
<p>This method performs the following steps when called:</p>
411+
<emu-alg>
412+
1. Let _promise_ be the *this* value.
413+
1. If IsPromise(_promise_) is *false*, throw a *TypeError* exception.
414+
1. Let _C_ be ? SpeciesConstructor(_promise_, %Promise%).
415+
1. Let _resultCapability_ be ? NewPromiseCapability(_C_).
416+
1. Return PerformPromiseThen(_promise_, _onFulfilled_, _onRejected_, _resultCapability_).
417+
</emu-alg>
418+
419+
<emu-clause id="sec-performpromisethen" type="abstract operation">
420+
<h1>
421+
PerformPromiseThen (
422+
_promise_: a Promise,
423+
_onFulfilled_: an ECMAScript language value,
424+
_onRejected_: an ECMAScript language value,
425+
optional _resultCapability_: a PromiseCapability Record,
426+
): an ECMAScript language value
427+
</h1>
428+
<dl class="header">
429+
<dt>description</dt>
430+
<dd>It performs the “then” operation on _promise_ using _onFulfilled_ and _onRejected_ as its settlement actions. If _resultCapability_ is passed, the result is stored by updating _resultCapability_'s promise. If it is not passed, then PerformPromiseThen is being called by a specification-internal operation where the result does not matter.</dd>
431+
</dl>
432+
<emu-alg>
433+
1. Assert: IsPromise(_promise_) is *true*.
434+
1. If _resultCapability_ is not present, then
435+
1. Set _resultCapability_ to *undefined*.
436+
1. If IsCallable(_onFulfilled_) is *false*, then
437+
1. Let _onFulfilledJobCallback_ be ~empty~.
438+
1. Else,
439+
1. Let _onFulfilledJobCallback_ be HostMakeJobCallback(_onFulfilled_).
440+
1. If IsCallable(_onRejected_) is *false*, then
441+
1. Let _onRejectedJobCallback_ be ~empty~.
442+
1. Else,
443+
1. Let _onRejectedJobCallback_ be HostMakeJobCallback(_onRejected_).
444+
1. <ins>Let _snapshot_ be AsyncContextSnapshot().</ins>
445+
1. Let _fulfillReaction_ be the PromiseReaction Record { [[Capability]]: _resultCapability_, [[Type]]: ~fulfill~, [[Handler]]: _onFulfilledJobCallback_, <ins>[[PromiseAsyncContextSnapshot]]: _snapshot_</ins> }.
446+
1. Let _rejectReaction_ be the PromiseReaction Record { [[Capability]]: _resultCapability_, [[Type]]: ~reject~, [[Handler]]: _onRejectedJobCallback_, <ins>[[PromiseAsyncContextSnapshot]]: _snapshot_</ins>}.
447+
1. If _promise_.[[PromiseState]] is ~pending~, then
448+
1. Append _fulfillReaction_ to _promise_.[[PromiseFulfillReactions]].
449+
1. Append _rejectReaction_ to _promise_.[[PromiseRejectReactions]].
450+
1. Else if _promise_.[[PromiseState]] is ~fulfilled~, then
451+
1. Let _value_ be _promise_.[[PromiseResult]].
452+
1. Let _fulfillJob_ be NewPromiseReactionJob(_fulfillReaction_, _value_).
453+
1. Perform HostEnqueuePromiseJob(_fulfillJob_.[[Job]], _fulfillJob_.[[Realm]]).
454+
1. Else,
455+
1. Assert: The value of _promise_.[[PromiseState]] is ~rejected~.
456+
1. Let _reason_ be _promise_.[[PromiseResult]].
457+
1. If _promise_.[[PromiseIsHandled]] is *false*, perform HostPromiseRejectionTracker(_promise_, *"handle"*).
458+
1. Let _rejectJob_ be NewPromiseReactionJob(_rejectReaction_, _reason_).
459+
1. Perform HostEnqueuePromiseJob(_rejectJob_.[[Job]], _rejectJob_.[[Realm]]).
460+
1. Set _promise_.[[PromiseIsHandled]] to *true*.
461+
1. If _resultCapability_ is *undefined*, then
462+
1. Return *undefined*.
463+
1. Else,
464+
1. Return _resultCapability_.[[Promise]].
465+
</emu-alg>
466+
</emu-clause>
467+
</emu-clause>
468+
</emu-clause>
389469
</emu-clause>
390470

391471
<emu-clause id="sec-generator-objects">
@@ -792,7 +872,6 @@ <h1>
792872
</emu-clause>
793873

794874
<ins class="block">
795-
796875
<emu-clause id="sec-asynccontext-object">
797876
<h1>The AsyncContext Object</h1>
798877
<p>The AsyncContext object:</p>
@@ -1164,3 +1243,48 @@ <h1>Properties of AsyncContext.Variable Instances</h1>
11641243
</emu-clause>
11651244
</ins>
11661245
</emu-clause>
1246+
1247+
<emu-clause id="sec-managing-memory">
1248+
<h1>Managing Memory</h1>
1249+
1250+
<emu-clause id="sec-finalization-registry-objects">
1251+
<h1>FinalizationRegistry Objects</h1>
1252+
<p>A FinalizationRegistry is an object that manages registration and unregistration of cleanup operations that are performed when target objects and symbols are garbage collected.</p>
1253+
1254+
<emu-clause id="sec-finalization-registry-constructor">
1255+
<h1>The FinalizationRegistry Constructor</h1>
1256+
<p>The <dfn variants="FinalizationRegistrys">FinalizationRegistry</dfn> constructor:</p>
1257+
<ul>
1258+
<li>is <dfn>%FinalizationRegistry%</dfn>.</li>
1259+
<li>
1260+
is the initial value of the *"FinalizationRegistry"* property of the global object.
1261+
</li>
1262+
<li>
1263+
creates and initializes a new FinalizationRegistry when called as a constructor.
1264+
</li>
1265+
<li>
1266+
is not intended to be called as a function and will throw an exception when called in that manner.
1267+
</li>
1268+
<li>
1269+
may be used as the value in an `extends` clause of a class definition. Subclass constructors that intend to inherit the specified `FinalizationRegistry` behaviour must include a `super` call to the `FinalizationRegistry` constructor to create and initialize the subclass instance with the internal state necessary to support the `FinalizationRegistry.prototype` built-in methods.
1270+
</li>
1271+
</ul>
1272+
1273+
<emu-clause id="sec-finalization-registry-cleanup-callback">
1274+
<h1>FinalizationRegistry ( _cleanupCallback_ )</h1>
1275+
<p>This function performs the following steps when called:</p>
1276+
<emu-alg>
1277+
1. If NewTarget is *undefined*, throw a *TypeError* exception.
1278+
1. If IsCallable(_cleanupCallback_) is *false*, throw a *TypeError* exception.
1279+
1. Let _finalizationRegistry_ be ? OrdinaryCreateFromConstructor(NewTarget, *"%FinalizationRegistry.prototype%"*, « [[Realm]], [[CleanupCallback]], [[Cells]], [[FinalizationRegistryAsyncContextSnapshot]] »).
1280+
1. Let _fn_ be the active function object.
1281+
1. Set _finalizationRegistry_.[[Realm]] to _fn_.[[Realm]].
1282+
1. Set _finalizationRegistry_.[[CleanupCallback]] to HostMakeJobCallback(_cleanupCallback_).
1283+
1. Set _finalizationRegistry_.[[Cells]] to a new empty List.
1284+
1. <ins>Set _finalizationRegistry_.[[FinalizationRegistryAsyncContextSnapshot]] to AsyncContextSnapshot().</ins>
1285+
1. Return _finalizationRegistry_.
1286+
</emu-alg>
1287+
</emu-clause>
1288+
</emu-clause>
1289+
</emu-clause>
1290+
</emu-clause>

0 commit comments

Comments
 (0)