-
Notifications
You must be signed in to change notification settings - Fork 48.8k
[Flight] Serialize already resolved Promises as debug models #33588
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
10000 Already on GitHub? Sign in to your account
Conversation
920e9b8
to
e865e01
Compare
One thing to consider here is that calling It can also hide unhandled promise rejections. Adding two then listeners before it has a chance to run its downstream listeners can break async stack traces. So we might want to do something about the timing of when or if we call .then. |
One issue is that if you pass a Promise as props, and then await it later. It will have already been serialized in a halted form because we don't wait for the second time it occurs. It would probably be better to wait until the end of the stream to read all the promises in one tick. |
…nformation (#33592) Stacked on #33588, #33589 and #33590. This lets us automatically show the resolved value in the UI. <img width="863" alt="Screenshot 2025-06-22 at 12 54 41 AM" src="https://github.com/user-attachments/assets/a66d1d5e-0513-4767-910c-5c7169fc2df4" /> We can also show rejected I/O that may or may not have been handled with the error message. <img width="838" alt="Screenshot 2025-06-22 at 12 55 06 AM" src="https://github.com/user-attachments/assets/e0a8b6ae-08ba-46d8-8cc5-efb60956a1d1" /> To get this working we need to keep the Promise around for longer so that we can access it once we want to emit an async sequence. I do this by storing the WeakRefs but to ensure that the Promise doesn't get garbage collected, I keep a WeakMap of Promise to the Promise that it depended on. This lets the VM still clean up any Promise chains that have leaves that are cleaned up. So this makes Promises liv 8000 e until the last Promise downstream is done. At that point we can go back up the chain to read the values out of them. Additionally, to get the best possible value we don't want to get a Promise that's used by internals of a third-party function. We want the value that the first party gets to observe. To do this I had to change the logic for which "await" to use, to be the one that is the first await that happened in user space. It's not enough that the await has any first party at all on the stack - it has to be the very first frame. This is a little sketchy because it relies on the `.then()` call or `await` call not having any third party wrappers. But it gives the best object since it hides all the internals. For example when you call `fetch()` we now log that actual `Response` object.
We already support serializing the values of instrumented Promises as debug values such as in console logs. However, we don't support plain native promises.
This waits a microtask to see if we can read the value within a microtask and if so emit it. This is so that we can still close the connection.
Otherwise, we emit a "halted" row into its row id which replaces the old "Infinite Promise" reference.
We could potentially wait until the end of the render before cancelling so that if it resolves before we exit we can still include its value but that would require a bit more work. Ideally we'd have a way to get these lazily later anyway.