( request: Request, task: Task, node: AsyncSequence, visited: Set<AsyncSequence | ReactDebugInfo>, cutOff: number, )
| 2251 | } |
| 2252 | |
| 2253 | function visitAsyncNode( |
| 2254 | request: Request, |
| 2255 | task: Task, |
| 2256 | node: AsyncSequence, |
| 2257 | visited: Set<AsyncSequence | ReactDebugInfo>, |
| 2258 | cutOff: number, |
| 2259 | ): void | null | PromiseNode | IONode { |
| 2260 | if (visited.has(node)) { |
| 2261 | // It's possible to visit them same node twice when it's part of both an "awaited" path |
| 2262 | // and a "previous" path. This also gracefully handles cycles which would be a bug. |
| 2263 | return null; |
| 2264 | } |
| 2265 | visited.add(node); |
| 2266 | let previousIONode = null; |
| 2267 | // First visit anything that blocked this sequence to start in the first place. |
| 2268 | if (node.previous !== null && node.end > request.timeOrigin) { |
| 2269 | previousIONode = visitAsyncNode( |
| 2270 | request, |
| 2271 | task, |
| 2272 | node.previous, |
| 2273 | visited, |
| 2274 | cutOff, |
| 2275 | ); |
| 2276 | if (previousIONode === undefined) { |
| 2277 | // Undefined is used as a signal that we found a suitable aborted node and we don't have to find |
| 2278 | // further aborted nodes. |
| 2279 | return undefined; |
| 2280 | } |
| 2281 | } |
| 2282 | switch (node.tag) { |
| 2283 | case IO_NODE: { |
| 2284 | return node; |
| 2285 | } |
| 2286 | case UNRESOLVED_PROMISE_NODE: { |
| 2287 | return previousIONode; |
| 2288 | } |
| 2289 | case PROMISE_NODE: { |
| 2290 | if (node.end <= request.timeOrigin) { |
| 2291 | // This was already resolved when we started this render. It must have been either something |
| 2292 | // that's part of a start up sequence or externally cached data. We exclude that information. |
| 2293 | // The technique for debugging the effects of uncached data on the render is to simply uncache it. |
| 2294 | return previousIONode; |
| 2295 | } |
| 2296 | const awaited = node.awaited; |
| 2297 | let match: void | null | PromiseNode | IONode = previousIONode; |
| 2298 | if (awaited !== null) { |
| 2299 | const ioNode = visitAsyncNode(request, task, awaited, visited, cutOff); |
| 2300 | if (ioNode === undefined) { |
| 2301 | // Undefined is used as a signal that we found a suitable aborted node and we don't have to find |
| 2302 | // further aborted nodes. |
| 2303 | return undefined; |
| 2304 | } else if (ioNode !== null) { |
| 2305 | // This Promise was blocked on I/O. That's a signal that this Promise is interesting to log. |
| 2306 | // We don't log it yet though. We return it to be logged by the point where it's awaited. |
| 2307 | // The ioNode might be another PromiseNode in the case where none of the AwaitNode had |
| 2308 | // unfiltered stacks. |
| 2309 | if (ioNode.tag === PROMISE_NODE) { |
| 2310 | // If the ioNode was a Promise, then that means we found one in user space since otherwise |
no test coverage detected