Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prior work: Dart zones #78

Open
pschiffmann opened this issue Apr 6, 2024 · 6 comments
Open

Prior work: Dart zones #78

pschiffmann opened this issue Apr 6, 2024 · 6 comments

Comments

@pschiffmann
Copy link

In Dart, you can use zones to achieve the same goal as you're going for with this proposal. Dart zones are not a 3rd party library, but are provided by the runtime - that means they work with async/await. I haven't seen them mentioned in the "prior arts" section, so I thought I'd mention them here. If you were aware of them already, kindly close this issue. :-)

Here is how you can translate the AsyncContext.Variable example to the Dart zone API:

import "dart:async";
import "dart:math";

var asyncVar = {};

main() {
  // Dart programs start in the `main`function, so I have moved the zone demo code to `main_`.
  runZoned(main_, zoneValues: {asyncVar: "top"});
}

main_() {
  randomTimeout().then((_) {
    print(Zone.current[asyncVar]); // => "top"

    runZoned(() async {
      print(Zone.current[asyncVar]); // => "A"
      await randomTimeout();
      print(Zone.current[asyncVar]); // => "A"
    }, zoneValues: {asyncVar: "A"});
  });

  runZoned(() async {
    print(Zone.current[asyncVar]); // => "B"
    await randomTimeout();
    print(Zone.current[asyncVar]); // => "B"
  }, zoneValues: {asyncVar: "B"});

  print(Zone.current[asyncVar]); // => "top"
}

randomTimeout() {
  return Future.delayed(Duration(milliseconds: Random().nextInt(1000)));
}

My two cents on the API: I personally prefer the Dart zone API design over the proposal for two reasons:

  1. In Dart, you don't have to create a Variable, you could just as well use any other value as a zone key. You can still have private variables, by using a module private object (i.e. the equivalent of const asyncVar = Symbol()) as key. But you can also use other objects as zone keys, which might be handy in certain situations.
  2. You can set multiple zone keys at once. For example if you have two variables asyncVar1 and asyncVar2 and you want to set values for both of them, with the current proposal, you'd have to write it like this, right? asyncVar1.run("A", () => asyncVar2.run("B", () => ...)). With the Dart zone API, you can do a single runZoned(..., { asyncVar1: "A", asyncVar2: "B" }) instead.
@littledan
Copy link
Member

Sounds like the Dart zones feature is pretty equivalent/analogous in functionality; am I understanding the code correctly? I wonder how they've resolved some of the issues we're currently looking into about the context where various callbacks run in.

@Jamesernator
Copy link

Jamesernator commented Apr 9, 2024

The old js zones proposal was basically the same thing, and is already mentioned in prior arts.

In Dart, you don't have to create a Variable, you could just as well use any other value as a zone key. You can still have private variables, by using a module private object (i.e. the equivalent of const asyncVar = Symbol()) as key. But you can also use other objects as zone keys, which might be handy in certain situations.

While not the only objection to Zones, the mere ability to do this was one of the major objections. AsyncContext.Variable was very specifically proposed to avoid sharing any objects unless you have access to them.

You can set multiple zone keys at once. For example if you have two variables asyncVar1 and asyncVar2 and you want to set values for both of them, with the current proposal, you'd have to write it like this, right? asyncVar1.run("A", () => asyncVar2.run("B", () => ...)). With the Dart zone API, you can do a single runZoned(..., { asyncVar1: "A", asyncVar2: "B" }) instead.

What is a use case here? The main use cases this proposal has been designed for (schedulers, tracing, loggers) all only need a single variable.

Like it would not be that hard to spec a AsyncContext.runAll([[asyncVar1, val1], [asyncVar2, val2]], cb) or similar, but withou motivating use cases what is the point?

@pschiffmann
Copy link
Author

I wonder how they've resolved some of the issues we're currently looking into about the context where various callbacks run in.

Dart zones are not implemented in user land, they are deeply integrated into the Dart VM and (probably?) built into the VM scheduler in C++.

The old js zones proposal was basically the same thing, and is already mentioned in prior arts.

Dart zones were actually shipped and can be used. I don't know if that's useful/relevant for the spec process. Based on the API similarity, Domenic was probably aware of the Dart implementation when he wrote his proposal, but I thought I'd mention it in case it's new for someone else in here.

@pschiffmann
Copy link
Author

You can set multiple zone keys at once.

What is a use case here? The main use cases this proposal has been designed for (schedulers, tracing, loggers) all only need a single variable.

Frameworks like Angular and NestJS support dependency injection through an annotation based system. But that requires a compile step (at least for now). And you can only annotate classes, not functions; so you have to wrap all your logic in Service classes.

Here is an example. Let's say you want to store a user uploaded profile picture file in AWS S3. With current NestJS, you'd have to define a service class to utilize their DI mechanism:

@Injectable()
class ProfilePictureUploadService {
  constructor(private awsService: AwsService, private logger: Logger) {}

  async uploadProfilePicture(user: User, file: File) {
    const uploadPath = `user-uploads/${user.id}/${file.name}`;
    try {
      await this.awsService.uploadFile(uploadPath, file);
    } catch(e) {
      this.logger.error(e);
    }
  }
}

Using AsyncContext.Variable, you can just write a simple function. You don't need the class boilerplate anymore.

async function uploadProfilePicture(user: User, file: File) {
  const awsService = awsServiceVar.get();
  const logger = loggerVar.get();
  try {
    await awsService.uploadFile(uploadPath, file);
  } catch(e) {
    logger.error(e);
  }
}

Complex NestJS and Angular apps can have dozens of service classes. AsyncContext.runAll([[asyncVar1, val1], [asyncVar2, val2]], cb) would be quite useful to provide all of them to my application.

@pschiffmann
Copy link
Author

In Dart, you don't have to create a Variable, you could just as well use any other value as a zone key.

While not the only objection to Zones, the mere ability to do this was one of the major objections. AsyncContext.Variable was very specifically proposed to avoid sharing any objects unless you have access to them.

Thinking about it again, the proposal approach makes more sense. I was thinking about a hypothetical use case where I might want to use a runtime value as a key. But if I really need to do that, I can just do:

const wm = new WeakMap();
wm.set(myRuntimeValue, new AsyncContext.Variable());

I agree that the AsyncContext.Variable API is more convenient than the zone API 99.9% of the time. Now that I realized it's also equally powerful, I do prefer the proposal API too. :)

@jridgewell
Copy link
Member

While not the only objection to Zones, the mere ability to do this was one of the major objections. AsyncContext.Variable was very specifically proposed to avoid sharing any objects unless you have access to them.

Agreed. We cannot give access to other Variables, but nothing is stopping a user from using an object with many keys in their own Variable. Just don't mutate it, you'll have a bad time.

AsyncContext.runAll([[asyncVar1, val1], [asyncVar2, val2]], cb) would be quite useful to provide all of them to my application.

#60 is related. While we may not do a Disposable, I wouldn't be opposed to a runAll(…) that sets multiple variables at once.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants