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).
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/[email protected]/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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With