99
1010import type { Wakeable } from 'shared/ReactTypes' ;
1111
12+ import * as http from 'http' ;
1213import * as https from 'https' ;
1314
1415import { readCache } from 'react/unstable-cache' ;
1516
16- type FetchOptions = { |
17- method ? : string ,
18- headers ? : any ,
19- redirect ? : 'follow' | 'manual' | 'error' ,
20- // Not yet implemented
21- signal ? : any ,
22- body ? : any ,
23- | } ;
24-
2517type FetchResponse = { |
2618 // Properties
2719 headers : any ,
2820 ok : boolean ,
2921 redirected : boolean ,
3022 status : number ,
3123 statusText : string ,
32- type : 'basic' | 'opaqueredirect' ,
24+ type : 'basic' ,
3325 url : string ,
3426 // Methods
3527 arrayBuffer ( ) : ArrayBuffer ,
@@ -38,131 +30,30 @@ type FetchResponse = {|
3830 text ( ) : string ,
3931| } ;
4032
41- const defaultOptions : FetchOptions = {
42- method : 'GET' ,
43- headers : { } ,
44- redirect : 'follow' ,
45- } ;
46-
47- const assign = Object [ 'as' + 'sign' ] ;
48-
49- function makeRequest (
33+ function nodeFetch (
5034 url : string ,
51- fetchOptions : FetchOptions ,
52- done : any => void ,
53- err : any => void ,
54- redirects : number = 0 ,
35+ options : mixed ,
36+ onResolve : any => void ,
37+ onReject : any => void ,
5538) : void {
56- const { hostname, pathname} = new URL ( url ) ;
57-
58- const options = assign ( { } , defaultOptions , fetchOptions , {
39+ const { hostname, pathname, search, port, protocol} = new URL ( url ) ;
40+ const nodeOptions = {
5941 hostname,
60- path : pathname ,
61- } ) ;
62-
63- const request = https . request ( options , response => {
64- if ( isRedirect ( response . statusCode ) ) {
65- if ( options . redirect === 'error' ) {
66- throw new Error ( 'Failed to fetch' ) ;
67- }
68-
69- let nextUrl = new URL ( response . headers . location , url ) ;
70- nextUrl = nextUrl . href ;
71-
72- if ( options . redirect === 'manual' ) {
73- createOpaqueRedirectResponse ( url ) ;
74- } else {
75- makeRequest ( nextUrl , fetchOptions , done , err , redirects + 1 ) ;
76- }
77-
78- return ;
79- }
80-
81- response . on ( 'data' , data => {
82- done ( createResponse ( url , redirects > 0 , response , data ) ) ;
83- } ) ;
42+ port,
43+ path : pathname + search ,
44+ // TODO: cherry-pick supported user-passed options.
45+ } ;
46+ const nodeImpl = protocol === 'https:' ? https : http ;
47+ const request = nodeImpl . request ( nodeOptions , response => {
48+ // TODO: support redirects.
49+ onResolve ( new Response ( response ) ) ;
8450 } ) ;
85-
8651 request . on ( 'error' , error => {
87- err ( error ) ;
52+ onReject ( error ) ;
8853 } ) ;
89-
9054 request . end ( ) ;
9155}
9256
93- function isRedirect ( code : number ) : boolean {
94- switch ( code ) {
95- case 301 :
96- case 302 :
97- case 303 :
98- case 307 :
99- case 308 :
100- return true ;
101- default :
102- return false ;
103- }
104- }
105-
106- function createOpaqueRedirectResponse ( url : string ) : FetchResponse {
107- return {
108- headers : { } ,
109- ok : false ,
110- redirected : false ,
111- status : 0 ,
112- statusText : '' ,
113- type : 'opaqueredirect' ,
114- url,
115- arrayBuffer ( ) {
116- throw new Error ( 'TODO' ) ;
117- } ,
118- blob ( ) {
119- throw new Error ( 'TODO' ) ;
120- } ,
121- json ( ) {
122- throw new Error ( 'TODO' ) ;
123- } ,
124- text ( ) {
125- throw new Error ( 'TODO' ) ;
126- } ,
127- } ;
128- }
129-
130- function createResponse (
131- url : string ,
132- redirected : boolean ,
133- response : any ,
134- data : Buffer ,
135- ) : FetchResponse {
136- return {
137- headers : response . headers ,
138- ok : response . statusCode >= 200 && response . statusCode < 300 ,
139- redirected,
140- status : response . statusCode ,
141- statusText : response . statusMessage ,
142- type : 'basic' ,
143- url,
144- arrayBuffer ( ) {
145- return Uint8Array . from ( data ) . buffer ;
146- } ,
147- blob ( ) {
148- // TODO: Not sure how to handle this just yet.
149- throw new Error ( 'TODO' ) ;
150- } ,
151- json ( ) {
152- return JSON . parse ( data . toString ( ) ) ;
153- } ,
154- text ( ) {
155- return data . toString ( ) ;
156- } ,
157- } ;
158- }
159-
160- function nodeFetch ( url : string , options : FetchOptions ) : Promise < any > {
161- return new Promise ( ( resolve , reject ) => {
162- return makeRequest ( url , options , resolve , reject ) ;
163- } ) ;
164- }
165-
16657const Pending = 0 ;
16758const Resolved = 1 ;
16859const Rejected = 2 ;
@@ -172,21 +63,21 @@ type PendingResult = {|
17263 value : Wakeable ,
17364| } ;
17465
175- type ResolvedResult = { |
66+ type ResolvedResult < V > = { |
17667 status : 1 ,
177- value : mixed ,
68+ value : V ,
17869| } ;
17970
18071type RejectedResult = { |
18172 status : 2 ,
18273 value : mixed ,
18374| } ;
18475
185- type Result = PendingResult | ResolvedResult | RejectedResult ;
76+ type Result < V > = PendingResult | ResolvedResult < V > | RejectedResult ;
18677
18778const fetchKey = { } ;
18879
189- function readResultMap ( ) : Map < string , Result > {
80+ function readResultMap ( ) : Map < string , Result < FetchResponse > > {
19081 const resources = readCache ( ) . resources ;
19182 let map = resources . get ( fetchKey ) ;
19283 if ( map === undefined ) {
@@ -196,31 +87,7 @@ function readResultMap(): Map<string, Result> {
19687 return map ;
19788}
19889
199- function toResult ( thenable ) : Result {
200- const result : Result = {
201- status : Pending ,
202- value : thenable ,
203- } ;
204- thenable . then (
205- value => {
206- if ( result . status === Pending ) {
207- const resolvedResult = ( ( result : any ) : ResolvedResult ) ;
208- resolvedResult . status = Resolved ;
209- resolvedResult . value = value ;
210- }
211- } ,
212- err => {
213- if ( result . status === Pending ) {
214- const rejectedResult = ( ( result : any ) : RejectedResult ) ;
215- rejectedResult . status = Rejected ;
216- rejectedResult . value = err ;
217- }
218- } ,
219- ) ;
220- return result ;
221- }
222-
223- function readResult ( result : Result ) {
90+ function readResult < T > (result: Result< T > ) {
22491 if ( result . status === Resolved ) {
22592 return result . value ;
22693 } else {
@@ -230,46 +97,75 @@ function readResult(result: Result) {
23097
23198function Response ( nativeResponse ) {
23299 this . headers = nativeResponse . headers ;
233- this . ok = nativeResponse . ok ;
234- this . redirected = nativeResponse . redirected ;
235- this . status = nativeResponse . status ;
236- this . statusText = nativeResponse . statusText ;
237- this . type = nativeResponse . type ;
100+ this . ok = nativeResponse . statusCode >= 200 && nativeResponse . statusCode < 300 ;
101+ this . redirected = false ; // TODO
102+ this . status = nativeResponse . statusCode ;
103+ this . statusText = nativeResponse . statusMessage ;
104+ this . type = 'basic' ;
238105 this . url = nativeResponse . url ;
239106
240107 this . _response = nativeResponse ;
241- this . _arrayBuffer = null ;
242108 this . _blob = null ;
243109 this . _json = null ;
244110 this . _text = null ;
111+
112+ const callbacks = [ ] ;
113+ function wake ( ) {
114+ // This assumes they won't throw.
115+ while ( callbacks . length > 0 ) {
116+ const cb = callbacks . pop ( ) ;
117+ cb ( ) ;
118+ }
119+ }
120+ const result : PendingResult = ( this . _result = {
121+ status : Pending ,
122+ value : {
123+ then ( cb ) {
124+ callbacks . push ( cb ) ;
125+ } ,
126+ } ,
127+ } ) ;
128+ const data = [ ] ;
129+ nativeResponse . on ( 'data' , chunk => data . push ( chunk ) ) ;
130+ nativeResponse . on ( 'end' , ( ) => {
131+ if ( result . status === Pending ) {
132+ const resolvedResult = ( ( result : any ) : ResolvedResult < Buffer > ) ;
133+ resolvedResult . status = Resolved ;
134+ resolvedResult . value = Buffer . concat ( data ) ;
135+ wake ( ) ;
136+ }
137+ } ) ;
138+ nativeResponse . on ( 'error' , err => {
139+ if ( result . status === Pending ) {
140+ const rejectedResult = ( ( result : any ) : RejectedResult ) ;
141+ rejectedResult . status = Rejected ;
142+ rejectedResult . value = err ;
143+ wake ( ) ;
144+ }
145+ } ) ;
245146}
246147
247148Response . prototype = {
248149 constructor : Response ,
249150 arrayBuffer ( ) {
250- return readResult (
251- this . _arrayBuffer ||
252- ( this . _arrayBuffer = toResult ( this . _response . arrayBuffer ( ) ) ) ,
253- ) ;
151+ const buffer = readResult ( this . _result ) ;
152+ return buffer ;
254153 } ,
255154 blob ( ) {
256- return readResult (
257- this . _blob || ( this . _blob = toResult ( this . _response . blob ( ) ) ) ,
258- ) ;
155+ // TODO: Is this needed?
156+ throw new Error ( 'Not implemented.' ) ;
259157 } ,
260158 json ( ) {
261- return readResult (
262- this . _json || ( this . _json = toResult ( this . _response . json ( ) ) ) ,
263- ) ;
159+ const buffer : Buffer = ( readResult ( this . _result ) : any ) ;
160+ return JSON . parse ( buffer . toString ( ) ) ;
264161 } ,
265162 text ( ) {
266- return readResult (
267- this . _text || ( this . _text = toResult ( this . _response . text ( ) ) ) ,
268- ) ;
163+ const buffer : Buffer = ( readResult ( this . _result ) : any ) ;
164+ return buffer . toString ( ) ;
269165 } ,
270166} ;
271167
272- function preloadResult ( url : string , options : FetchOptions ) : Result {
168+ function preloadResult ( url : string , options : mixed ) : Result < FetchResponse > {
273169 const map = readResultMap ( ) ;
274170 let entry = map . get ( url ) ;
275171 if ( ! entry ) {
@@ -280,23 +176,54 @@ function preloadResult(url: string, options: FetchOptions): Result {
280176 throw Error ( 'Unsupported option' ) ;
281177 }
282178 }
283- const thenable = nodeFetch ( url , options ) ;
284- entry = toResult ( thenable ) ;
179+ const callbacks = [ ] ;
180+ const wakeable = {
181+ then ( cb ) {
182+ callbacks . push ( cb ) ;
183+ } ,
184+ } ;
185+ const wake = ( ) => {
186+ // This assumes they won't throw.
187+ while ( callbacks . length > 0 ) {
188+ const cb = callbacks . pop ( ) ;
189+ cb ( ) ;
190+ }
191+ } ;
192+ const result : Result < FetchResponse > = (entry = {
193+ status : Pending ,
194+ value : wakeable ,
195+ } );
196+ nodeFetch(
197+ url,
198+ options,
199+ response => {
200+ if ( result . status === Pending ) {
201+ const resolvedResult = ( ( result : any ) : ResolvedResult < FetchResponse > ) ;
202+ resolvedResult . status = Resolved ;
203+ resolvedResult . value = response ;
204+ wake ( ) ;
205+ }
206+ } ,
207+ err => {
208+ if ( result . status === Pending ) {
209+ const rejectedResult = ( ( result : any ) : RejectedResult ) ;
210+ rejectedResult . status = Rejected ;
211+ rejectedResult . value = err ;
212+ wake ( ) ;
213+ }
214+ } ,
215+ ) ;
285216 map . set ( url , entry ) ;
286217 }
287218 return entry ;
288219}
289220
290- export function preload ( url : string , options : FetchOptions ) : void {
221+ export function preload ( url : string , options : mixed ) : void {
222+ preloadResult ( url , options ) ;
291223 // Don't return anything.
292224}
293225
294- export function fetch ( url : string , options : FetchOptions ) : Object {
226+ export function fetch ( url : string , options : mixed ) : Object {
295227 const result = preloadResult ( url , options ) ;
296- const nativeResponse = ( readResult ( result ) : any ) ;
297- if ( nativeResponse . _reactResponse ) {
298- return nativeResponse . _reactResponse ;
299- } else {
300- return ( nativeResponse . _reactResponse = new Response ( nativeResponse ) ) ;
301- }
228+ return ( readResult ( result ) : any ) ;
302229}
0 commit comments