( thenableState: ThenableState, thenable: Thenable<T>, index: number, )
| 105 | } |
| 106 | |
| 107 | export function trackUsedThenable<T>( |
| 108 | thenableState: ThenableState, |
| 109 | thenable: Thenable<T>, |
| 110 | index: number, |
| 111 | ): T { |
| 112 | if (__DEV__ && ReactSharedInternals.actQueue !== null) { |
| 113 | ReactSharedInternals.didUsePromise = true; |
| 114 | } |
| 115 | const trackedThenables = getThenablesFromState(thenableState); |
| 116 | const previous = trackedThenables[index]; |
| 117 | if (previous === undefined) { |
| 118 | trackedThenables.push(thenable); |
| 119 | } else { |
| 120 | if (previous !== thenable) { |
| 121 | // Reuse the previous thenable, and drop the new one. We can assume |
| 122 | // they represent the same value, because components are idempotent. |
| 123 | |
| 124 | if (__DEV__) { |
| 125 | const thenableStateDev: ThenableStateDev = (thenableState: any); |
| 126 | if (!thenableStateDev.didWarnAboutUncachedPromise) { |
| 127 | // We should only warn the first time an uncached thenable is |
| 128 | // discovered per component, because if there are multiple, the |
| 129 | // subsequent ones are likely derived from the first. |
| 130 | // |
| 131 | // We track this on the thenableState instead of deduping using the |
| 132 | // component name like we usually do, because in the case of a |
| 133 | // promise-as-React-node, the owner component is likely different from |
| 134 | // the parent that's currently being reconciled. We'd have to track |
| 135 | // the owner using state, which we're trying to move away from. Though |
| 136 | // since this is dev-only, maybe that'd be OK. |
| 137 | // |
| 138 | // However, another benefit of doing it this way is we might |
| 139 | // eventually have a thenableState per memo/Forget boundary instead |
| 140 | // of per component, so this would allow us to have more |
| 141 | // granular warnings. |
| 142 | thenableStateDev.didWarnAboutUncachedPromise = true; |
| 143 | |
| 144 | // TODO: This warning should link to a corresponding docs page. |
| 145 | console.error( |
| 146 | 'A component was suspended by an uncached promise. Creating ' + |
| 147 | 'promises inside a Client Component or hook is not yet ' + |
| 148 | 'supported, except via a Suspense-compatible library or framework.', |
| 149 | ); |
| 150 | } |
| 151 | } |
| 152 | |
| 153 | // Avoid an unhandled rejection errors for the Promises that we'll |
| 154 | // intentionally ignore. |
| 155 | thenable.then(noop, noop); |
| 156 | thenable = previous; |
| 157 | } |
| 158 | } |
| 159 | |
| 160 | if (__DEV__ && enableAsyncDebugInfo && thenable._debugInfo === undefined) { |
| 161 | // In DEV mode if the thenable that we observed had no debug info, then we add |
| 162 | // an inferred debug info so that we're able to track its potential I/O uniquely. |
| 163 | // We don't know the real start time since the I/O could have started much |
| 164 | // earlier and this could even be a cached Promise. Could be misleading. |
no test coverage detected