5

Speaking broadly, in NodeJS a program creates "an async context" whenever code runs that creates a callback-to-be-run-later via the Event Loop or the microtask queue. Network requests, setTimeout callbacks, Promises, etc.

In modern versions of NodeJs you have the (still experimental) async_hooks module to track the lifecycle of these asynchronous resources.

Previous versions of NodeJS had the now deprecated process.addAsyncListener -- kept alive by the async-listener polyfill package.

Does Deno have any built-in or third party functionality that allows userland code to track the creation of these asynchronous contexts? Or is there something about how Deno works that makes this an irrelevant concept?

Alana Storm
  • 164,128
  • 91
  • 395
  • 599
  • Well, through async functions you basically already have that functionality. So this was mostly a problem with promise chains which were hard to track, with async functions you already get proper callstacks etc. – Jonas Wilms Jun 24 '20 at 07:23
  • 2
    Thank you for responding Jonas -- but I'm not sure I follow what you're saying. I think we may be talking about different things. It is true that async/await makes it easier for an individual programmer to reason about their functions and not worry about the async contexts -- but even with async/await the entire javascript program is still executing out of order. I would like a way to write some code that can instrument code other people have written and track their invoked async contexts (which seem to be mostly promises in deno, and async/await offering a better syntax over those promises) – Alana Storm Jun 24 '20 at 16:30
  • Have you looked at proxying Promise and/or AsyncFunction? – mfulton26 Jun 25 '20 at 12:19
  • @mfulton26 That's not possible. You cannot proxy native implementations. Overwriting `then` might be doable, but I wouldn't try. – Bergi Jun 25 '20 at 12:48
  • @mfulton26 thank you for the suggestion I have not tried that. I presume that any changes I make to the global Promise or AsyncFunction object will only apply to my local module, and not to the rest of the program as a whole. Am I incorrect in this? (also, because it's not obvious from context, I'm trying to find a way to invisibly have these contexts tracked -- asking users "please change how you create your Promise or AsyncFunction classes" won't work for my case. – Alana Storm Jun 25 '20 at 21:09
  • @Bergi Thank you -- when you say you can't proxy the native implementations -- what do you mean? Do you mean that passing native implementations to `new Proxy(...)` will cause an error? Or that your changes won't persist outside the local module's scope? Or some third thing? – Alana Storm Jun 25 '20 at 21:10
  • 1
    @AlanStorm Yes, I'm saying that [proxies don't go well with native objects](https://stackoverflow.com/a/45966614/1048572) and that you cannot intercept the calls to the builtin Promise constructor by `async` functions. Proxies are pointless for this task. The best you can do is to intercept calls to the `then` method to propagate your contexts, but I wouldn't recommend it. Also I think it doesn't even work with `await` as that always uses the native `then` to schedule the continuation. – Bergi Jun 25 '20 at 21:23

1 Answers1

3

I don't think such functionality is publicly available at this moment or through 3rd party modules.

Briefly looked into the functionality async_hooks provide (might miss something important, please correct me), it seems that init and promiseResolve are more like the interesting ones in Deno (Deno hardly uses callbacks in its APIs).

The way Deno does privileged operations is by sending serialized text message (mostly JSON) and zero-copy buffers to the Rust side, and it receives messages from Rust side when Rust side invokes a callback with response messages. It would be interesting if we can intercept some of them by adding a small wrapping listener to some of the core message channel methods, e.g. Deno.core.dispatchByName (this one is used by almost all Deno privileged operations, async or sync. There are a lot of interesting other things available on Deno.core, see core/core.js in source. Somehow handleAsyncMsgFromRust which receives async messages from Rust is not exposed though).

Unfortunately Deno.core is currently frozen (actually I was the one responsible for this change) to avoid overwrite (such can cause hard crashes on upgrade as Deno.core hosts critical yet volatile internal API). Probably you could open an issue in the Deno repo to ask if others would like to implement some interface to allow user inject message-capturing callbacks to conceptual send and recv methods. (I am currently unable to contribute)

Kevin Qian
  • 2,532
  • 1
  • 16
  • 26
  • _nod_ thank you Kevin -- I had a parallel issue open and it sounds like what I want isn't currently available in Deno, but that people are thinking about it: https://github.com/denoland/deno/issues/6449. Also thank you for letting me know about Deno.core -- this doesn't seem to be documented via the standard library site, so I presume it's not a supported API, but it's always fun to poke at things you're not "allowed" to poke at. :) – Alana Storm Jun 24 '20 at 16:58