5

I'm trying to debug some (third party) code running in a browser context, and I'd like to track all downstream async function calls, pretty much exactly what async_hooks for nodejs provides. Essentially, something like https://github.com/mafintosh/why-is-node-running but in the browser. I'd settle for "something close", or even patching globals if I have to...

Anyone know of anything remotely close? This (https://stackoverflow.com/a/49245432/1256988) looked promising, but it doesn't appear to be working for sub-calls for me?!

I should point out that my practical use here is really just keeping a count of async function calls, so that at the end of my test, I know if I've "leaked" async calls.

I should additionally point out that this (https://github.com/AndreasMadsen/async-hook/issues/15) seems to indicate that I might be out of luck in the browser (though I'm ok with only approx. correct results).

Carson Farmer
  • 506
  • 3
  • 19
  • 1
    I don't think a drop-in replacement for `async_hooks` would be possible without native support. There are just _way_ too many web APIs that would need to be patched in order for this to be possible, and memory leaks would be very easy to accidentally introduce with a non-native patch since promises that haven't settled are allowed to be GC'd when they're not tied to open resources and it becomes impossible to fulfill or reject them. – Patrick Roberts Apr 05 '21 at 22:00
  • Ok, I kind of figured this was going to be the case. I'll leave this one open for a little longer, but your comment here might just be the answer :( – Carson Farmer Apr 13 '21 at 04:50

2 Answers2

2

Zone.js does a pretty good job of this. It's what Angular uses internally to track state across asynchronous calls but it does exist as a separate API. It works by patching a whole load of browser APIs (such as setTimeout, event subscription, etc.)

Here's an example piece of HTML showing it in use. It runs up a couple of Zones (thread contexts, basically) and does asynchronous work in each zone. You can see the current zone magically being passed around during the flow of asynchronous work.

<html>
  <script src="https://unpkg.com/zone.js@0.11.5/bundles/zone.umd.js"></script>
  <script>
    const rootZone = Zone.current;

    // Create two zones (thread contexts, basically)
    const zone1 = rootZone.fork({
      name: 'zone1',
      properties: { pizzaInfo: { topping: 'pepperoni' } },
    });
    const zone2 = rootZone.fork({
      name: 'zone2',
      properties: { pizzaInfo: { topping: 'pineapple' } },
    });

    // A function which will trigger some asynchronous work
    // Doesn't know anything about zones, but magically the
    // work will run in whatever zone launched it.
    function someWork() {
      setTimeout(someAsyncWork, 1000);
    }

    // Some asynchronous work
    function someAsyncWork() {
      const initialZone = Zone.current;
      const pizzaInfo = initialZone.get('pizzaInfo');

      console.log(`someAsyncWork: in zone ${initialZone.name}`);
      console.log(`someAsyncWork: best pizza topping for this zone is ${pizzaInfo.topping}`);

      // Have some async fun with promises and timers
      delay(500).then(yetMoreWork);
    }

    function yetMoreWork() {
      const zone = Zone.current;
      console.log(`yetMoreWork: in zone ${zone.name}`);
    }

    function delay(millis) {
      return new Promise((resolve) => setTimeout(resolve, millis));
    }

    zone1.run(someWork);
    zone2.run(someWork);
  </script>
</html>

(Here's a JSFiddle for that code: https://jsfiddle.net/rbkxd512/)

There's a quite a lot for Zone.js to patch in a browser, so it's broken into modules which you can select from depending on whether you care about multimedia APIs, network APIs, etc.

  • Only problem with Zone.js is that it can't monkey patch async / await functions. Really need a native solution to this. – Bryan Rayner Dec 09 '22 at 18:36
1

Dexie.js has implemented a thing called PSD which allows for tracking async context -- even with native promises.

I pulled it out into its own package here: https://github.com/vlcn-io/model/tree/main/ts/packages/zone

You can see example of tracking an async function here: https://github.com/vlcn-io/model/blob/bb2907ca5034b345bfedd42af21944e10543b01d/ts/packages/value/src/transaction.ts#L106-L125

Note that the API isn't very user friendly yet and can be complicated to get the zone tracking set up correctly.

Real world tests and uses to get you started: https://github.com/vlcn-io/model/tree/main/ts/packages/value

Matt Wonlaw
  • 12,146
  • 5
  • 42
  • 44