Something like async_hooks for the browser?

Viewed 449

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).

1 Answers

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.

Related