Add support for async disposable#1073
Conversation
commit: |
Symbol.asyncDispose on tasks to enable `await using…|
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)
|
Please review the scope feature |
|
|
||
| [Symbol.asyncDispose](): Promise<void>; |
There was a problem hiding this comment.
@joshamaju Do we need to document this? I'm not sure what the best practice is on documenting whether something is an explicitly manageable resource.
|
@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 |
Issues from initial review have been addressed
|
We should add it to resource page to say that they're supported out of the box with this version. |
|
I've added a section to the upgrade guide documentation |
|
@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? |
|
@joshamaju What do you think about making the Array returned by using [scope] = createScope(); |
|
Is that even possible? I don't think it's possible |
|
So I just checked. Can only be used like this |
|
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? ☝🏻 |
|
Maybe |
|
I don't think that would work either, would it? We could make the object Iterable for backwards compatibility if it did. |
|
Yep, it works |
|
Should it be a separate function i.e |
|
So we could define a |
|
How do you mean? |
export function createScope(
parent: Scope = global,
): [Scope, () => Future<void>] {
let [scope, destroy] = createScopeInternal(parent);
let tuple = [scope, () => parent.run(destroy)];
let disposable = 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. |
|
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 // 🤣 🤣
declare function createScope(parent?: Scope): Scope & AsyncDisposable & [Scope, () => Promise<void>]; |
|
This is horrendous, but it works: export function createScope(
parent: Scope = global,
): Scope & AsyncDisposable & [Scope, () => Future<void>] {
let [scope, destroy] = createScopeInternal(parent);
let dispose = () => parent.run(destroy);
let tuple = [scope, dispose];
Object.defineProperty(scope, Symbol.iterator, {
enumerable: false,
value: tuple[Symbol.iterator].bind(tuple),
});
Object.defineProperty(scope, Symbol.asyncDispose, {
enumerable: false,
value: dispose,
})
return scope as unknown as Scope & AsyncDisposable & [Scope, () => Future<void>];
} |
|
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(); |
|
@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. |
Motivation
Cleanup effection task at application/effection boundary to avoid hanging operations.
issue
Approach
Add support for native Javascript async disposables.
Alternate Designs
Possible Drawbacks or Risks
TODOs and Open Questions
Learning
Screenshots