|
7 | 7 | * @flow
|
8 | 8 | */
|
9 | 9 |
|
10 |
| -import type {ReactNodeList} from 'shared/ReactTypes'; |
| 10 | +import type { |
| 11 | + ReactNodeList, |
| 12 | + Thenable, |
| 13 | + PendingThenable, |
| 14 | + FulfilledThenable, |
| 15 | + RejectedThenable, |
| 16 | +} from 'shared/ReactTypes'; |
11 | 17 |
|
12 | 18 | import isArray from 'shared/isArray';
|
13 | 19 | import {
|
@@ -75,6 +81,68 @@ function getElementKey(element: any, index: number): string {
|
75 | 81 | return index.toString(36);
|
76 | 82 | }
|
77 | 83 |
|
| 84 | +function noop() {} |
| 85 | + |
| 86 | +function resolveThenable<T>(thenable: Thenable<T>): T { |
| 87 | + switch (thenable.status) { |
| 88 | + case 'fulfilled': { |
| 89 | + const fulfilledValue: T = thenable.value; |
| 90 | + return fulfilledValue; |
| 91 | + } |
| 92 | + case 'rejected': { |
| 93 | + const rejectedError = thenable.reason; |
| 94 | + throw rejectedError; |
| 95 | + } |
| 96 | + default: { |
| 97 | + if (typeof thenable.status === 'string') { |
| 98 | + // Only instrument the thenable if the status if not defined. If |
| 99 | + // it's defined, but an unknown value, assume it's been instrumented by |
| 100 | + // some custom userspace implementation. We treat it as "pending". |
| 101 | + // Attach a dummy listener, to ensure that any lazy initialization can |
| 102 | + // happen. Flight lazily parses JSON when the value is actually awaited. |
| 103 | + thenable.then(noop, noop); |
| 104 | + } else { |
| 105 | + // This is an uncached thenable that we haven't seen before. |
| 106 | + |
| 107 | + // TODO: Detect infinite ping loops caused by uncached promises. |
| 108 | + |
| 109 | + const pendingThenable: PendingThenable<T> = (thenable: any); |
| 110 | + pendingThenable.status = 'pending'; |
| 111 | + pendingThenable.then( |
| 112 | + fulfilledValue => { |
| 113 | + if (thenable.status === 'pending') { |
| 114 | + const fulfilledThenable: FulfilledThenable<T> = (thenable: any); |
| 115 | + fulfilledThenable.status = 'fulfilled'; |
| 116 | + fulfilledThenable.value = fulfilledValue; |
| 117 | + } |
| 118 | + }, |
| 119 | + (error: mixed) => { |
| 120 | + if (thenable.status === 'pending') { |
| 121 | + const rejectedThenable: RejectedThenable<T> = (thenable: any); |
| 122 | + rejectedThenable.status = 'rejected'; |
| 123 | + rejectedThenable.reason = error; |
| 124 | + } |
| 125 | + }, |
| 126 | + ); |
| 127 | + } |
| 128 | + |
| 129 | + // Check one more time in case the thenable resolved synchronously. |
| 130 | + switch (thenable.status) { |
| 131 | + case 'fulfilled': { |
| 132 | + const fulfilledThenable: FulfilledThenable<T> = (thenable: any); |
| 133 | + return fulfilledThenable.value; |
| 134 | + } |
| 135 | + case 'rejected': { |
| 136 | + const rejectedThenable: RejectedThenable<T> = (thenable: any); |
| 137 | + const rejectedError = rejectedThenable.reason; |
| 138 | + throw rejectedError; |
| 139 | + } |
| 140 | + } |
| 141 | + } |
| 142 | + } |
| 143 | + throw thenable; |
| 144 | +} |
| 145 | + |
78 | 146 | function mapIntoArray(
|
79 | 147 | children: ?ReactNodeList,
|
80 | 148 | array: Array<React$Node>,
|
@@ -106,9 +174,14 @@ function mapIntoArray(
|
106 | 174 | invokeCallback = true;
|
107 | 175 | break;
|
108 | 176 | case REACT_LAZY_TYPE:
|
109 |
| - throw new Error( |
110 |
| - 'Cannot render an Async Component, Promise or React.Lazy inside React.Children. ' + |
111 |
| - 'We recommend not iterating over children and just rendering them plain.', |
| 177 | + const payload = (children: any)._payload; |
| 178 | + const init = (children: any)._init; |
| 179 | + return mapIntoArray( |
| 180 | + init(payload), |
| 181 | + array, |
| 182 | + escapedPrefix, |
| 183 | + nameSoFar, |
| 184 | + callback, |
112 | 185 | );
|
113 | 186 | }
|
114 | 187 | }
|
@@ -211,16 +284,19 @@ function mapIntoArray(
|
211 | 284 | );
|
212 | 285 | }
|
213 | 286 | } else if (type === 'object') {
|
214 |
| - // eslint-disable-next-line react-internal/safe-string-coercion |
215 |
| - const childrenString = String((children: any)); |
216 |
| - |
217 | 287 | if (typeof (children: any).then === 'function') {
|
218 |
| - throw new Error( |
219 |
| - 'Cannot render an Async Component, Promise or React.Lazy inside React.Children. ' + |
220 |
| - 'We recommend not iterating over children and just rendering them plain.', |
| 288 | + return mapIntoArray( |
| 289 | + resolveThenable((children: any)), |
| 290 | + array, |
| 291 | + escapedPrefix, |
| 292 | + nameSoFar, |
| 293 | + callback, |
221 | 294 | );
|
222 | 295 | }
|
223 | 296 |
|
| 297 | + // eslint-disable-next-line react-internal/safe-string-coercion |
| 298 | + const childrenString = String((children: any)); |
| 299 | + |
224 | 300 | throw new Error(
|
225 | 301 | `Objects are not valid as a React child (found: ${
|
226 | 302 | childrenString === '[object Object]'
|
|
0 commit comments