Open sgammon opened 1 year ago
With the notable exception of https://console.spec.whatwg.org/ standards don't do a lot of active work around debugging.
Stringification of URL objects is there because it helps when writing code. That's why it has toJSON()
as well.
Some kind of approximate stringification of Headers
, Request
, and Response
would not I think.
toJSON()
for Headers
could be reasonable though. Just have to make sure we properly account for Set-Cookie
when designing that.
console.log()
for these objects providing a richer experience also seems quite reasonable. Perhaps the Console Standard can help with that somehow? cc @domfarolino
Thank you @annevk for reading and responding.
Stringification of URL objects is there because it helps when writing code.
I couldn't agree more. Really, a console.log
-able representation of each of these objects is the core of this request. Thank you and kudos to the Firefox team for a smooth dev experience with Headers
.
toJSON()
forHeaders
could be reasonable though
The best alternative I've found so far, but definitely open to anything better:
Object.fromEntries(headers.entries())
Under the hood in the debug logs for the Axios fetch adapter, we need to call that fromEntries(...entries())
for each log statement, which is presumably rather expensive vs. what an internal representation could do.
Console-wise, this is a tough one! The spec is quite vague to allow for implementation-specific formatting that is judged to be "maximally useful". Under https://console.spec.whatwg.org/#printer, see https://console.spec.whatwg.org/#generic-javascript-object-formatting and https://console.spec.whatwg.org/#optimally-useful-formatting.
So aside from filing bugs on implementations requesting a more useful custom visual representation of complicated objects, the only other alternative I can imagine is some sort of hook that console could provide to let complicated objects bypass those definitions and provide their own representation composed of primitives. Perhaps something like allowing objects to supply their own "console string builder" or "console object builder" etc., that the Console Standard can hook into and print the result of, instead of applying the vague definitions that I linked to above. That probably wouldn't be too hard to do and may well have the desired results you're looking for.
Feel free to file a bug on Console for this, but I will say that standard doesn't get a whole lot of love these days so any large contributions are more likely to materialize with help from the community!
@domfarolino I see. thank you for the references, I'll take a look.
What about a custom [Symbol.*]
which provides a function tailored to emit a better console representation? I understand stringification is already a point of API compatibility, so surely an opt-in upgrade for implementers would be best if this is fixed by spec.
Feel free to file a bug on Console for this, but I will say that standard doesn't get a whole lot of love these days so any large contributions are more likely to materialize with help from the community!
Forgive me, I'm new to standards contribution :) I would love to file a contribution for this if it is a welcome PR from WhatWG's perspective. How do I get started? I'll take a look at that spec, contribution guides, etc. Would I just fork, and file a PR change to the spec itself?
I happen to be implementing the Fetch API in a different project entirely which involves a JS runtime, so I have an opportunity to dogfood those new Console extensions myself and iterate quickly if that is at all helpful.
What about a custom [Symbol.*] which provides a function tailored to emit a better console representation? I understand stringification is already a point of API compatibility, so surely an opt-in upgrade for implementers would be best if this is fixed by spec.
Whether to pursue the "internal Console Standard hook" route vs something like a new web-exposed well-known Symbol name route depends on how important it is to have the pretty-printable values exposed to script. For most of these objects (perhaps except Headers
?) Anne seemed to be leaning more towards the idea of a Console Standard hook so I guess we should figure that out. I don't know what would be best for these Fetch objects, or if Anne has strong opinions either way. That seems like the first thing to resolve though.
A Console-only hook would be pretty easy, but the implementation is vague and might be hard for vendors to prioritize, and you don't get any interesting values exposed to script. A web platform-exposed hook would give you more information, and could allow you to print things without requiring modifications to the implementation-specific Console logging, but it is a larger addition to the platform, especially with Symbols and all.
Thoughts @annevk ?
I think as far as console.log()
goes we could perhaps stress stronger that implementations are encouraged to provide a lot of additional context about platform objects being logged and maybe give some examples in the console.log()
specification as to what that might look like.
However, this really only applies to the examples in OP where the object is directly passed and not stringified first or some such.
And on reflection for those cases what browsers show for Request
and Response
seems reasonable, but it could be tailored to be even better.
For Headers
I can see making the case that it should show more of the internal structure.
I suspect we cannot actually make normative requirements about what the representation will look like so hooks of any kind are probably not needed here.
Hello esteemed WhatWG Fetch authors,
I am a library author helping to implement
fetch
support downstream in the popular[^1] Axios library. For the uninitiated, Axios helps developers fetch data using various adapters (including XHR and Node'shttp
module), and on various platforms (browsers and Node).Now that Fetch is very broadly supported[^2] (congrats, and thank you for all your hard work!), it is seeing new support in libraries such as Axios (hi! 👋🏻). Before proceeding, I just wanted to say that the Fetch API is one of the smoothest and clearest API interfaces offered by the web, in my humble opinion; it is spreading because it is easy to use, refreshingly obvious in its behaviors and assumptions, and general enough to cover an extremely broad set of cases, from
await fetch('...')
to more complex scenarios with cancellation and streams.That being said, there is exactly one place where frequent interaction with the Fetch API seems to fall short: string representations for
Headers
,Request
, andResponse
. I'm writing today to see if the WhatWG can help clear this up across supporting implementations.Paragon case:
URL
When developing with
URL
,Request
, andResponse
(referred to herein as ancillary Fetch API objects), the developer may often need to obtain a stringified representation, either for use elsewhere in their software, or for debugging purposes.URL
objects are a bright spot of support for this: they are interchangeable withstring
objects for many intents and purposes within the realm of Fetch, and indeed in the browser in general:This behavior is consistent across all three major browser vendors:
Immediately, in the debug window, I can see the URL itself, and even browse the components of the URL. Fantastic!
Server-side runtimes also nail this:
Engines are remarkably consistent about
URL
, even across platforms and runtimes. Arguably,URL
used in this way is a light value class, i.e. just a strongly-typed and validated shell around the well-formed string. This case remains sadly isolated from other Fetch API objects, though.Problem case 1:
Headers
I first encountered problems with
Headers
when diagnosing issues in the early Fetch adapter code. Let's try this snippet:What do we get?
??
At first glance, the
Headers
object we just created looks empty. This is obviously not the case: the object has the header we put in it, it just isn't showing in the string representation. This can be confirmed withheaders.has("x")
orheaders.get("x")
, each of which return the expected value.Platforms and browsers are not as consistent on this point: Chrome is arguably the worst example (cross-filed here), but everyone fails this test except Firefox, with Node/Deno getting by (barely):
{}
) when logged (!!). Can't be stringified or JSONified.Problem case 2:
Request
/Response
I won't bore you guys with the setup, let's take a look at how they behave across platforms:
Lovely debugging experience logging it directly, and, while I understand binary exchange is the norm with HTTP/2 in broad adoption, whatever happened to
Request[GET https://github.com]
? The virtue of HTTP1.x was easy debuggability, and there's no reason we have to give that up in a post-binary world.One more test, this time with
Response
:No
HTTP 200 OK (51k bytes)
? Okay, okay. I've made my point.Why this is a problem
If
URL
behaves as a value class around a well-formedURL
string (you can't create aURL
object from an invalid URL string), one might assume thatHeaders
, too, behaves as a value class around a well-formedmap<!string, string[]>
. This would make sense, especially since, as we all know, HTTP headers can be repeated, and so you always need a bit of ceremony around a regularMap
to handle headers properly.This assumption, though, is broken in a number of ways:
1) The developer can't JSON-stringify the headers object, as one would expect to be the case with a regular object 2) Printing the object does not reliably show the contents the developer is most likely to be interested in, as one would expect with a regular object
These are arguably cosmetic assumption breakages, but this problem gets a bit deeper. In order to diagnose or assert based on the full state of the
Headers
object, users are forced to iterate, carry + sync a second object, or defer use ofHeaders
until the moment before a request is fired, which sadly neutralizes the benefits the API surface can provide (looking at you, repeated headers).Avoiding the
Headers
object is lame because it's so great. Iterating carries the risk of exploding input. Carrying a second object and keeping it in sync has many pitfalls. Thus, the developer experience is surprisingly hampered by this one API incongruity.Alternatives considered
1) Additional observable API detail. Perhaps WhatWG doesn't want to mandate disclosure of these internal object details unless and until the developer expresses an intent to obtain them: this allows implementors more latitude to privately cache or defer calculations with greater cover.
2) Implementor freedom. Perhaps consensus is too high a bar for this kind of functionality to be written directly into the specification, or perhaps implementors have voiced a desire to control this aspect of how the Fetch API behaves.
3) Maybe this has been discussed. If so, my apologies, especially if a decision has been made on this topic. I was not able to find any related issues after a search on GitHub.
Thank you in advance for consideration of this request. ✌🏻
[^1]: Publicly (on GitHub), 7.8 million projects use Axios. 103k of these are other packages which themselves constitute additional transitive addressable users. [^2]: CanIUse places
fetch
support at 97.05% at the time of this writing.