Any way to detect an async stack trace (like Chrome devtools does)?

#!/JoePea joe at trusktr.io
Sat Jul 11 04:21:51 UTC 2020


Hello Kai! That example is so meta with its own output showing the
numbers relative to the code including its own output! :)

That's what I originally wanted to do, but that doesn't give us an
async stack trace, it only gives a sync stack trace (I presume within
the current event loop task).

For example:

```js
const originalFetch = globalThis.fetch

globalThis.fetch = function(...args) {
  const stack = new Error().stack
  console.log(stack)
  return originalFetch.apply(globalThis, args)
}

const sleep = t => new Promise(r => setTimeout(r, t))

async function one() {
  console.log('1')
  await sleep(10)
  two()
}

async function two() {
  console.log('2')
  await sleep(10)
  three()
}

async function three() {
  console.log('3')
  await sleep(10)
  return await fetch('https://unpkg.com/three@0.118.3')
}

async function main() {
  await one()
}

main()
```

Output:

```
1
2
3
Error
    at globalThis.fetch (pen.js:5)
    at three (pen.js:27)
```

Live example:

https://codepen.io/trusktr/pen/b8fd92752b4671268f516ad3804869e4?editors=1010

I opened a request for this over here:
https://github.com/tc39/proposal-error-stacks/issues/34

#!/JoePea

On Fri, Jul 10, 2020 at 1:21 PM kai zhu <kaizhu256 at gmail.com> wrote:
>
> >  (I want to detect traces in third-party code installed locally).
>
> 1. here's 15-line javascript-hack to trace async-fetch-calls from 3rd-party-cdn-library
>
> ```html
> <!doctype html>
> <html lang="en">
> <body>
> <h1>test.html</h1>
> <script>
> (function () {
> /*
>  * 15-line-javascript-hack to trace async-fetch-calls
>  * enable hack by adding search-query "?modeDebugFetch=1" to web-url
>  */
>     "use strict";
>     if ((/\bmodeDebugFetch=1\b/).test(location.search)) {
>         let fetch0;
>         fetch0 = globalThis.fetch;
>         globalThis.fetch = function (...argList) {
>             let errStack = new Error().stack;
>             console.error("\n\nfetch-call-arguments:");
>             console.error(JSON.stringify(argList, undefined, 4));
>             console.error("\n\nfetch-call-trace:");
>             console.error(errStack);
>             return fetch0(...argList);
>         };
>     }
> }());
> </script>
> <!-- 3rd-party-cdn-library -->
> <script src="https://unpkg.com/http-client/umd/http-client.js"></script>
> <script>
> (async function foo() {
>     "use strict";
>     let thirdParty = window.HTTPClient;
>     let myFetch = thirdParty.createFetch(
>         thirdParty.base("https://api.stripe.com/v1"),
>         thirdParty.accept("application/json")
>     );
>     let response = await myFetch("/customers/5");
>     console.log(response.jsonData);
> /*
> dev-console-output - http://localhost:8081/test.html?modeDebugFetch=1
> fetch-call-arguments:
> [
>     "https://api.stripe.com/v1/customers/5",
>     {
>         "headers": {
>             "Accept": "application/json"
>         },
>         "responseHandlers": [
>             null
>         ]
>     }
> ]
> fetch-call-trace:
> Error
>     at globalThis.fetch (test.html?modeDebugFetch=1:16)
>     at http-client.js:194
>     at http-client.js:127
>     at http-client.js:217
>     at http-client.js:126
>     at http-client.js:154
>     at http-client.js:95
>     at foo (test.html?modeDebugFetch=1:35)
>     at test.html?modeDebugFetch=1:64
> */
> }());
> </script>
> </body>
> ```
>
>
> 2. here's real-world-hack added to npm-cli.js to debug its http-requests
>
> ```diff
> C:\Program Files\nodejs\node_modules\npm>git diff
> diff --git a/bin/npm-cli.js b/bin/npm-cli.js
> index 561dec0..98cafb8 100644
> --- a/bin/npm-cli.js
> +++ b/bin/npm-cli.js
> @@ -2,17 +2,6 @@
>  ;(function () { // wrapper in case we're in module_context mode
> +// hack - debug http-request
> +let httpRequest;
> +httpRequest = require("https").request.bind(require("https"));
> +require("https").request = function (...argList) {
> +    if (process.env.NPM_DEBUG) {
> +        console.error(
> +            "npm - httpRequest - " + JSON.stringify(argList.slice(0, 2), undefined, 4)
> +        );
> +    }
> +    return httpRequest(...argList);
> +};
>    // windows: running "npm blah" in this folder will invoke WSH, not node.
>
> C:\Program Files\nodejs\node_modules\npm>
> ```
>
> ```mingw-bash
> $ NPM_DEBUG=1 npm publish foo
> # console-ouput:
> # npm - httpRequest - [
> #     {
> #         "protocol": "https:",
> #         "href": "https://registry.npmjs.org/foo",
> #         "method": "GET",
> #         "headers": {
> #             "connection": [
> #                 "keep-alive"
> #             ],
> #             "user-agent": [
> #                 "npm/6.13.4 node/v12.16.1 win32 x64"
> #             ],
> #             ...
> ```
>
>
>
>
>
> On Fri, Jul 10, 2020 at 3:34 AM #!/JoePea <joe at trusktr.io> wrote:
>>
>> Thanks for the idea! That's similar to what I thought would be necessary. I was hoping to monkey patch `fetch()` to have it get the trace if any `fetch` call, but they would not be async stack traces. For async traces it would require instrumentation by injecting code similar to yours (I want to detect traces in third-party code installed locally).
>>
>> #!/JoePea
>>
>> On Wed, Jul 8, 2020, 2:13 AM kai zhu <kaizhu256 at gmail.com> wrote:
>>>
>>> here's a simple throwaway-function you can wrap around promises like fetch
>>> to get the caller's async-stack-trace `promiseWithErrorStack`:
>>>
>>> ```html
>>> <!doctype html>
>>> <html lang="en">
>>> <body>
>>> <h1>test.html</h1>
>>> <script>
>>> (async function foo() {
>>>     function promiseWithErrorStack(promise) {
>>>     /*
>>>      * this function will append current-stack to any err caught from <promise>
>>>      */
>>>         let errStack;
>>>         errStack = new Error().stack;
>>>         return new Promise(function (resolve, reject) {
>>>             promise.then(resolve).catch(function (err) {
>>>                 // append current errStack to err.stack
>>>                 if (err && typeof err.stack === "string") {
>>>                     err.stack += "\n" + errStack;
>>>                 }
>>>                 reject(err);
>>>             });
>>>         });
>>>     }
>>>     await promiseWithErrorStack(fetch("https://example.com")); // at foo (test.html:23)
>>>
>>>     /*
>>>     console-output:
>>>
>>>     Uncaught (in promise) TypeError: Failed to fetch
>>>     Error
>>>         at promiseWithErrorStack (test.html:12)
>>>         at foo (test.html:23) // async-stack-trace
>>>         at test.html:32
>>>     */
>>> }());
>>> </script>
>>> </body>
>>> ```
>>>
>>> On Tue, Jul 7, 2020 at 11:54 PM #!/JoePea <joe at trusktr.io> wrote:
>>>>
>>>> Is there some way (perhaps by patching methods on objects?) so we can
>>>> track async call stacks?
>>>>
>>>> When we pause code in devtools, we are able to see the async stack
>>>> trace of the code.
>>>>
>>>> What I'd like to do is to effectively detect the same thing as
>>>> devtools does at some point in any code. As a specific example, I'd
>>>> like to detect the async stack trace of any call to `fetch`.
>>>>
>>>> Is such a thing possible with runtime code?
>>>>
>>>> Or would it require instrumentation of the source code?
>>>>
>>>> #!/JoePea
>>>> _______________________________________________
>>>> es-discuss mailing list
>>>> es-discuss at mozilla.org
>>>> https://mail.mozilla.org/listinfo/es-discuss


More information about the es-discuss mailing list