Skip to content

Commit ff349b9

Browse files
Closure Teamcopybara-github
Closure Team
authored andcommitted
Add a polyfill for AsyncDisposableStack.
PiperOrigin-RevId: 734591184
1 parent e7858c8 commit ff349b9

File tree

3 files changed

+255
-0
lines changed

3 files changed

+255
-0
lines changed

externs/es6.js

+54
Original file line numberDiff line numberDiff line change
@@ -2498,3 +2498,57 @@ DisposableStack.prototype.defer = function(onDispose) {};
24982498
* @return {!DisposableStack}
24992499
*/
25002500
DisposableStack.prototype.move = function() {};
2501+
2502+
/**
2503+
* @record
2504+
*/
2505+
function AsyncDisposable() {}
2506+
2507+
/**
2508+
* @return {void}
2509+
*/
2510+
AsyncDisposable.prototype[Symbol.asyncDispose] = function() {};
2511+
2512+
/**
2513+
* An AsyncDisposableStack is an object that can be used to contain one or more
2514+
* resources that should be disposed of together. The resources may be disposed
2515+
* of asynchronously.
2516+
*
2517+
* @constructor
2518+
*/
2519+
function AsyncDisposableStack() {}
2520+
2521+
/**
2522+
* @type {boolean}
2523+
*/
2524+
AsyncDisposableStack.prototype.disposed;
2525+
2526+
/**
2527+
* @return {!Promise<void>}
2528+
*/
2529+
AsyncDisposableStack.prototype.disposeAsync = function() {};
2530+
/**
2531+
* @return {!Promise<void>}
2532+
*/
2533+
AsyncDisposableStack.prototype[Symbol.asyncDispose] = function () {};
2534+
/**
2535+
* @param {!AsyncDisposable|!Disposable|null|undefined} disposable
2536+
* @return {!AsyncDisposable|!Disposable|null|undefined}
2537+
*/
2538+
AsyncDisposableStack.prototype.use = function(disposable) {};
2539+
/**
2540+
* @template T
2541+
* @param {T} value
2542+
* @param {function(T): (void|!Promise<void>)} onDispose
2543+
* @return {T}
2544+
*/
2545+
AsyncDisposableStack.prototype.adopt = function(value, onDispose) {};
2546+
/**
2547+
* @param {function(): (void|!Promise<void>)} onDispose
2548+
* @return {void}
2549+
*/
2550+
AsyncDisposableStack.prototype.defer = function(onDispose) {};
2551+
/**
2552+
* @return {!AsyncDisposableStack}
2553+
*/
2554+
AsyncDisposableStack.prototype.move = function() {};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
/*
2+
* Copyright 2025 The Closure Compiler Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
'require util/global';
18+
'require util/polyfill';
19+
'require util/defineproperty';
20+
'require es6/dispose';
21+
'require es6/promise/promise';
22+
23+
$jscomp.polyfill('AsyncDisposableStack', function(orig) {
24+
if (orig) {
25+
return orig;
26+
}
27+
var ILLEGAL_AFTER_DISPOSAL = 'Forbidden after disposed.';
28+
var INVALID_DISPOSABLE = 'Invalid Disposable';
29+
/**
30+
* @constructor
31+
* @implements {AsyncDisposable}
32+
* @nosideeffects
33+
*/
34+
function AsyncDisposableStack() {
35+
/** @private {!Array<function(): (void|Promise<void>)>} */
36+
this.stack_ = [];
37+
/** @private {boolean} */
38+
this.actuallyDisposed_ = false;
39+
}
40+
$jscomp.defineProperty(AsyncDisposableStack.prototype, 'disposed', {
41+
configurable: true,
42+
get: function() {
43+
return this.actuallyDisposed_;
44+
}
45+
});
46+
// https://tc39.es/proposal-explicit-resource-management/#sec-asyncdisposablestack.prototype.disposeAsync
47+
AsyncDisposableStack.prototype.disposeAsync = function() {
48+
// Steps 3-4
49+
if (this.disposed) {
50+
return Promise.resolve();
51+
}
52+
this.actuallyDisposed_ = true; // Step 5
53+
// For steps 6-7 we're moving into
54+
// https://tc39.es/proposal-explicit-resource-management/#sec-disposeresources
55+
var errorEncountered = false;
56+
var err;
57+
var result = Promise.resolve();
58+
59+
var makeClosure = function(onDispose) {
60+
return function() {
61+
return onDispose();
62+
};
63+
};
64+
// Step 1 – note moving backwards through the stack
65+
for (var i = this.stack_.length - 1; i >= 0; i--) {
66+
// "hint" is always async in an AsyncDisposableStack
67+
// https://tc39.es/proposal-explicit-resource-management/#sec-dispose
68+
result = result.then(makeClosure(this.stack_[i])).catch(function(e) {
69+
// Step 1.b.i
70+
if (errorEncountered) {
71+
err = new SuppressedError(e, err, '');
72+
} else {
73+
errorEncountered = true;
74+
err = e;
75+
76+
}
77+
});
78+
}
79+
// Steps 2-3
80+
this.stack_.length = 0;
81+
return result.then(function() {
82+
// Step 4
83+
if (errorEncountered) {
84+
throw err;
85+
}
86+
// Back out to disposeAsync
87+
// Step 8-9
88+
return undefined;
89+
});
90+
};
91+
// https://tc39.es/proposal-explicit-resource-management/#sec-asyncdisposablestack.prototype-@@asyncDispose
92+
AsyncDisposableStack.prototype[Symbol.asyncDispose] =
93+
AsyncDisposableStack.prototype.disposeAsync;
94+
95+
// https://tc39.es/proposal-explicit-resource-management/#sec-asyncdisposablestack.prototype.use
96+
AsyncDisposableStack.prototype.use = function(disposable) {
97+
// Step 2-3
98+
if (this.disposed) {
99+
throw new ReferenceError(ILLEGAL_AFTER_DISPOSAL);
100+
}
101+
// In Step 4 we're moving into
102+
// https://tc39.es/proposal-explicit-resource-management/#sec-adddisposableresource
103+
// which then call create disposable resource and get dispose method
104+
if (disposable == null) {
105+
// 1.b - gotta force an await in this case.
106+
this.stack_.push(function() {
107+
return Promise.resolve();
108+
});
109+
} else if (
110+
disposable[Symbol.asyncDispose] != null &&
111+
typeof disposable[Symbol.asyncDispose] === 'function') {
112+
// Per get dispose method with an async hint, first we check for
113+
// asyncDispose, then we check for dispose.
114+
var disposeMethod = disposable[Symbol.asyncDispose];
115+
// According to get dispose method, we define a promise-returning closure
116+
// that resolves or rejects based on the result of the dispose method.
117+
this.stack_.push(function() {
118+
return Promise.resolve().then(function() {
119+
return disposeMethod.call(disposable);
120+
});
121+
});
122+
} else if (typeof disposable[Symbol.dispose] === 'function') {
123+
var disposeMethod = disposable[Symbol.dispose];
124+
this.stack_.push(function() {
125+
return Promise.resolve().then(function() {
126+
// Note that we don't return the result of disposeMethod.
127+
// A promise returned from a sync dispose method must be ignored,
128+
// and must not be awaited. However we do want to reject if the
129+
// dispose method synchronously throws.
130+
// See 1.b.ii of
131+
// https://tc39.es/proposal-explicit-resource-management/#sec-getdisposemethod
132+
disposeMethod.call(disposable);
133+
});
134+
});
135+
} else {
136+
// See i.b.iii of
137+
// https://tc39.es/proposal-explicit-resource-management/#sec-createdisposableresource
138+
throw new TypeError(INVALID_DISPOSABLE);
139+
}
140+
// Step 5
141+
return disposable;
142+
};
143+
144+
// https://tc39.es/proposal-explicit-resource-management/#sec-asyncdisposablestack.prototype.adopt
145+
AsyncDisposableStack.prototype.adopt = function(value, onDispose) {
146+
// Step 2-3
147+
if (this.disposed) {
148+
throw new ReferenceError(ILLEGAL_AFTER_DISPOSAL);
149+
}
150+
// Step 4
151+
if (typeof onDispose !== 'function') {
152+
throw new TypeError(INVALID_DISPOSABLE);
153+
}
154+
// Step 5-7
155+
this.stack_.push(function() {
156+
return Promise.resolve().then(function() {
157+
return onDispose.call(undefined, value);
158+
});
159+
});
160+
// Step 8
161+
return value;
162+
};
163+
164+
// https://tc39.es/proposal-explicit-resource-management/#sec-asyncdisposablestack.prototype.defer
165+
AsyncDisposableStack.prototype.defer = function(onDispose) {
166+
// Step 2-3
167+
if (this.disposed) {
168+
throw new ReferenceError(ILLEGAL_AFTER_DISPOSAL);
169+
}
170+
// Step 4
171+
if (typeof onDispose !== 'function') {
172+
throw new TypeError(INVALID_DISPOSABLE);
173+
}
174+
// Step 5
175+
this.stack_.push(onDispose.bind(undefined));
176+
// Step 6
177+
return undefined;
178+
};
179+
180+
// https://tc39.es/proposal-explicit-resource-management/#sec-asyncdisposablestack.prototype.move
181+
AsyncDisposableStack.prototype.move = function() {
182+
// Steps 2-3
183+
if (this.disposed) {
184+
throw new ReferenceError(ILLEGAL_AFTER_DISPOSAL);
185+
}
186+
// Step 4-5
187+
var newDisposableStack = new AsyncDisposableStack();
188+
// Step 6
189+
newDisposableStack.stack_ = this.stack_;
190+
// Step 7
191+
this.stack_ = [];
192+
// Step 8
193+
this.actuallyDisposed_ = true;
194+
// Step 9
195+
return newDisposableStack;
196+
};
197+
198+
return AsyncDisposableStack;
199+
// probably ES2026
200+
}, 'es_next', 'es5');

src/com/google/javascript/jscomp/js/es6_runtime.js

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
* @suppress {uselessCode}
2020
*/
2121
'require es6/array';
22+
'require es6/async_disposable_stack';
2223
'require es6/dispose';
2324
'require es6/disposable_stack';
2425
'require es6/globalthis';

0 commit comments

Comments
 (0)