Uh oh!
There was an error while loading. Please reload this page.
Add support for async disposable - #1073
Conversation
commit: |
Symbol.asyncDispose on tasks to enable `await using…Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
joshamaju
commented
Jan 9, 2026
How do we move this forward? |
cowboyd
left a comment
There was a problem hiding this comment.
I agree, let's push this over the finish line.
- Formatting changes: don't worry about it, I'll fix them up.
- Question about the types
- We should also add
asyncDisposetoScope(along with a test)
Uh oh!
There was an error while loading. Please reload this page.
joshamaju
commented
Jan 11, 2026
Please review the scope feature |
Uh oh!
There was an error while loading. Please reload this page.
cowboyd
commented
Jan 14, 2026
@joshamaju I removed the formatting changes, and moved the scope implementation into ScopeInternal. The only remaining issue is documentation. I'm not sure how much is required for |
Uh oh!
There was an error while loading. Please reload this page.
Issues from initial review have been addressed
taras
commented
Jan 15, 2026
We should add it to resource page to say that they're supported out of the box with this version. |
joshamaju
commented
Jan 15, 2026
I've added a section to the upgrade guide documentation |
cowboyd
commented
Jan 16, 2026
@taras I'm not sure the resources page isn't really the right place because resources are implicitly cleaned up. Only tasks and scopes implement AsyncDisposable, but not sure where the place to explain that is. The section on typescript maybe? Rosetta Stone? |
cowboyd
commented
Feb 3, 2026
@joshamaju What do you think about making the Array returned by using [scope]=createScope(); |
joshamaju
commented
Feb 3, 2026
Is that even possible? I don't think it's possible |
So I just checked. Can only be used like this |
cowboyd
commented
Feb 3, 2026
Yeah, it's not possible to use destructing assignment with explicitly managed resources.... another reason I don't like them. I had an AI tell me it was though, and so I got my hopes up. 🤣🤣🤣 I just think it will be awkward to use without being able to do it with a one-liner. Maybe we need a new method? using scope=disposableScope();
using scope=createDisposableScope();something like those or another alternative? ☝🏻 |
joshamaju
commented
Feb 3, 2026
Maybe |
cowboyd
commented
Feb 3, 2026
I don't think that would work either, would it? We could make the object Iterable for backwards compatibility if it did. |
joshamaju
commented
Feb 3, 2026
Yep, it works |
joshamaju
commented
Feb 3, 2026
Should it be a separate function i.e |
cowboyd
commented
Feb 4, 2026
So we could define a |
joshamaju
commented
Feb 4, 2026
How do you mean? |
exportfunctioncreateScope(parent: Scope=global,): [Scope,()=>Future<void>]{let[scope,destroy]=createScopeInternal(parent);lettuple=[scope,()=>parent.run(destroy)];letdisposable=Object.defineProperty(scope,Symbol.asyncDispose,{value: ()=>parent.run(destroy)});Object.defineProperty(tuple,'scope',{value: disposable});}This would then let us say: using { scope }=createScope(); |
Yeah, it's like I thought. Binding expressions are not supported inside using declarations microsoft/TypeScript#55527 It's yet another example of how explicit resource management was railroaded through without concern over how much incongruence with existing patterns it would introduce. |
cowboyd
commented
Feb 4, 2026
I think we can still do it, but it means we really have to abuse the runtime and the type system to make it work. I think what we can do is define // 🤣 🤣 declarefunctioncreateScope(parent?: Scope): Scope&AsyncDisposable&[Scope,()=>Promise<void>]; |
cowboyd
commented
Feb 4, 2026
This is horrendous, but it works: exportfunctioncreateScope(parent: Scope=global,): Scope&AsyncDisposable&[Scope,()=>Future<void>]{let[scope,destroy]=createScopeInternal(parent);letdispose=()=>parent.run(destroy);lettuple=[scope,dispose];Object.defineProperty(scope,Symbol.iterator,{enumerable: false,value: tuple[Symbol.iterator].bind(tuple),});Object.defineProperty(scope,Symbol.asyncDispose,{enumerable: false,value: dispose,})returnscopeasunknownasScope&AsyncDisposable&[Scope,()=>Future<void>];} |
joshamaju
commented
Feb 5, 2026
Is the goal that a user can use it in one of the following ways? await using scope=createScope();or like this const[scope]=createScope(); |
cowboyd
commented
Feb 5, 2026
@joshamaju yes, that's the idea, although the second way is a no-no. You should always capture the It is a bit disgusting, but it let's you consume it either way. |
joshamaju
commented
Feb 6, 2026
@cowboyd I've added the update. I'm soo going to abuse |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
joshamaju
commented
Feb 9, 2026
I've applied the changes as discussed |
efb2591 to
9ffb088Comparecowboyd
commented
Feb 10, 2026
@joshamaju I rebased on v4, squashed all the commits, fixed the formatting and added documentation to the repo as well as the PR. Anything else we should cover? |
JavaScript now has a native scope based mechanism for cleanup called "Explicit Resource Management" This integrates this spec in two ways: 1. Every Task implements the AsyncDisposable api directly. 2. When calling `createScope()` the scope returned also implements AsyncDisposable interface. There is a bit of hackery that we needed to do with `createScope()` so that it can support both: ```ts await using scope = createScope(); ``` and ```ts let [scope, destroy] = createScope(); ```
9ffb088 to
8a0c134Comparejoshamaju
commented
Feb 15, 2026
I should push updates with the |
f08cf53 to
8a0c134Compare
Motivation
Cleanup effection task at application/effection boundary to avoid hanging operations.
JavaScript now has a native scope based mechanism for cleanup called "Explicit Resource Management"
Approach
This integrates this spec in two ways:
createScope()the scope returned also implements AsyncDisposable interfaceThere is a bit of hackery that we needed to do with
createScope()so that it can support both:and