@@ -60,37 +60,36 @@ const installDisposableStack = (): void => {
6060 "Symbol.asyncDispose" ,
6161 ) ;
6262
63- const forceOwnedPolyfill = hasBrokenNativeDisposableStack (
63+ const forceOwnedPolyfill = hasBrokenNativeDisposableStackSync (
6464 symbolDispose ,
6565 symbolAsyncDispose ,
6666 ) ;
6767
68- if ( forceOwnedPolyfill || typeof globalThis . DisposableStack !== "function" ) {
69- defineGlobalValue (
70- globalThis ,
71- "DisposableStack" ,
72- createDisposableStackPolyfill ( symbolDispose ) ,
73- ) ;
74- }
75-
7668 if (
7769 forceOwnedPolyfill ||
70+ typeof globalThis . DisposableStack !== "function" ||
7871 typeof globalThis . AsyncDisposableStack !== "function"
7972 ) {
80- defineGlobalValue (
81- globalThis ,
82- "AsyncDisposableStack" ,
83- createAsyncDisposableStackPolyfill ( symbolDispose , symbolAsyncDispose ) ,
84- ) ;
73+ installOwnedDisposableStackPolyfills ( symbolDispose , symbolAsyncDispose ) ;
74+ return ;
8575 }
76+
77+ void hasBrokenNativeDisposableStackAsync (
78+ symbolDispose ,
79+ symbolAsyncDispose ,
80+ ) . then ( ( hasBrokenNative ) => {
81+ if ( hasBrokenNative ) {
82+ installOwnedDisposableStackPolyfills ( symbolDispose , symbolAsyncDispose ) ;
83+ }
84+ } ) ;
8685} ;
8786
8887/**
8988 * Some runtimes expose native DisposableStack APIs but fail on valid usage
9089 * (notably AsyncDisposableStack.use with Symbol.dispose-only resources).
9190 * In that case we switch to the owned polyfill for deterministic behavior.
9291 */
93- const hasBrokenNativeDisposableStack = (
92+ const hasBrokenNativeDisposableStackSync = (
9493 symbolDispose : symbol ,
9594 symbolAsyncDispose : symbol ,
9695) : boolean => {
@@ -124,14 +123,81 @@ const hasBrokenNativeDisposableStack = (
124123 const asyncDisposableStack = new AsyncDisposableStackCtor ( ) ;
125124 asyncDisposableStack . use ( disposableResource ) ;
126125 asyncDisposableStack . use ( asyncDisposableResource ) ;
127- void asyncDisposableStack . disposeAsync ( ) ;
126+ const disposeResult = asyncDisposableStack . disposeAsync ( ) ;
127+ if ( isPromiseLike ( disposeResult ) ) {
128+ // Prevent unhandled rejections while the async probe below verifies behavior.
129+ void disposeResult . catch ( ( ) => {
130+ return ;
131+ } ) ;
132+ }
133+
134+ return false ;
135+ } catch {
136+ return true ;
137+ }
138+ } ;
139+
140+ const hasBrokenNativeDisposableStackAsync = async (
141+ symbolDispose : symbol ,
142+ symbolAsyncDispose : symbol ,
143+ ) : Promise < boolean > => {
144+ const DisposableStackCtor = globalThis . DisposableStack ;
145+ const AsyncDisposableStackCtor = globalThis . AsyncDisposableStack ;
146+
147+ if (
148+ typeof DisposableStackCtor !== "function" ||
149+ typeof AsyncDisposableStackCtor !== "function"
150+ ) {
151+ return false ;
152+ }
153+
154+ const disposableResource = {
155+ [ symbolDispose ] ( ) {
156+ return ;
157+ } ,
158+ } as unknown as Disposable ;
159+
160+ const asyncDisposableResource = {
161+ [ symbolAsyncDispose ] : async ( ) => {
162+ return ;
163+ } ,
164+ } as unknown as AsyncDisposable ;
165+
166+ try {
167+ const disposableStack = new DisposableStackCtor ( ) ;
168+ disposableStack . use ( disposableResource ) ;
169+ disposableStack . dispose ( ) ;
170+
171+ const asyncDisposableStack = new AsyncDisposableStackCtor ( ) ;
172+ asyncDisposableStack . use ( disposableResource ) ;
173+ asyncDisposableStack . use ( asyncDisposableResource ) ;
174+ await asyncDisposableStack . disposeAsync ( ) ;
128175
129176 return false ;
130177 } catch {
131178 return true ;
132179 }
133180} ;
134181
182+ const installOwnedDisposableStackPolyfills = (
183+ symbolDispose : symbol ,
184+ symbolAsyncDispose : symbol ,
185+ ) : void => {
186+ defineGlobalValue (
187+ globalThis ,
188+ "DisposableStack" ,
189+ createDisposableStackPolyfill ( symbolDispose ) ,
190+ ) ;
191+ defineGlobalValue (
192+ globalThis ,
193+ "AsyncDisposableStack" ,
194+ createAsyncDisposableStackPolyfill ( symbolDispose , symbolAsyncDispose ) ,
195+ ) ;
196+ } ;
197+
198+ const isPromiseLike = ( value : unknown ) : value is PromiseLike < unknown > =>
199+ typeof ( value as { then ?: unknown } | null ) ?. then === "function" ;
200+
135201type SymbolWithDisposable = SymbolConstructor & {
136202 dispose ?: symbol ;
137203 asyncDispose ?: symbol ;
@@ -140,7 +206,7 @@ type SymbolWithDisposable = SymbolConstructor & {
140206const suppressedErrorMessage = "An error was suppressed during disposal." ;
141207
142208const disposedMessage = ( className : string , method : string ) : string =>
143- `Cannot call ${ className } .prototype.${ method } on an already-disposed DisposableStack ` ;
209+ `Cannot call ${ className } .prototype.${ method } on an already-disposed ${ className } ` ;
144210
145211const defineGlobalValue = (
146212 target : object ,
0 commit comments