@samitouri / QOS-React-2 / commits / dd7f0deb94

[Blocks] Use native fetch (#18784)

* [Blocks] Use native fetch * Use the prototype * Support arrayBuffer() and blob() * ctor * Simplify * Use an expando * Keep a map of formats * Unused * Remove unnecessary second property read * Keep it simple * Store the original thenable

Dan Abramov committed Apr 30, 2020 at 01:24 UTC dd7f0deb947f836170cd1019dbbc2e44d30a21ce
3 files changed +94 -43
fixtures/blocks/src/Comments.js
+1 -3
@@ -10,9 +10,7 @@ import {fetch} from 'react-data/fetch';
10
11 function load(postId) {
12 return {
13 - comments: JSON.parse(
14 - fetch('http://localhost:3001/comments?postId=' + postId)
15 - ),
13 + comments: fetch('http://localhost:3001/comments?postId=' + postId).json(),
14 };
15 }
16
fixtures/blocks/src/Post.js
+4 -1
@@ -11,8 +11,10 @@ import {fetch} from 'react-data/fetch';
11 import loadComments from './Comments';
12
13 function load(params) {
14 + const postResponse = fetch('http://localhost:3001/posts/' + params.id);
15 return {
15 - post: JSON.parse(fetch('http://localhost:3001/posts/' + params.id)),
16 + post: postResponse.json(),
17 + meta: postResponse.status + ' ' + postResponse.statusText,
18 Comments: loadComments(params.id),
19 };
20 }
@@ -23,6 +25,7 @@ function Post(props, data) {
25 <h1>Post {data.post.id}</h1>
26 <h4>{data.post.title}</h4>
27 <p>{data.post.body}</p>
28 + <pre>{data.meta}</pre>
29 <hr />
30 <Suspense fallback={<p>Loading comments...</p>}>
31 <data.Comments />
packages/react-data/src/fetch/ReactDataFetch.js
+89 -39
@@ -32,6 +32,8 @@ type RejectedResult = {|
32
33 type Result = PendingResult | ResolvedResult | RejectedResult;
34
35 +// TODO: this is a browser-only version. Add a separate Node entry point.
36 +const nativeFetch = window.fetch;
37 const fetchKey = {};
38
39 function readResultMap(): Map<string, Result> {
@@ -44,50 +46,98 @@ function readResultMap(): Map<string, Result> {
46 return map;
47 }
48
47 -// TODO: options, auth, etc.
48 -export function fetch(url: string): Object {
49 - const map = readResultMap();
50 - const entry = map.get(url);
51 - if (entry === undefined) {
52 - let resolve = () => {};
53 - const wakeable: Wakeable = new Promise(r => {
54 - // TODO: should this be a plain thenable instead?
55 - resolve = r;
56 - });
57 - const result: Result = {
58 - status: Pending,
59 - value: wakeable,
60 - };
61 - map.set(url, result);
62 - const xhr = new XMLHttpRequest();
63 - xhr.onload = function() {
64 - // TODO: should we handle status codes?
65 - if (result.status !== Pending) {
66 - return;
49 +function toResult(thenable): Result {
50 + const result: Result = {
51 + status: Pending,
52 + value: thenable,
53 + };
54 + thenable.then(
55 + value => {
56 + if (result.status === Pending) {
57 + const resolvedResult = ((result: any): ResolvedResult);
58 + resolvedResult.status = Resolved;
59 + resolvedResult.value = value;
60 }
68 - const resolvedResult = ((result: any): ResolvedResult);
69 - resolvedResult.status = Resolved;
70 - resolvedResult.value = xhr.response;
71 - resolve();
72 - };
73 - xhr.onerror = function() {
74 - if (result.status !== Pending) {
75 - return;
61 + },
62 + err => {
63 + if (result.status === Pending) {
64 + const rejectedResult = ((result: any): RejectedResult);
65 + rejectedResult.status = Rejected;
66 + rejectedResult.value = err;
67 }
77 - const rejectedResult = ((result: any): RejectedResult);
78 - rejectedResult.status = Rejected;
79 - // TODO: use something else as the error value?
80 - rejectedResult.value = xhr;
81 - resolve();
82 - };
83 - xhr.open('GET', url);
84 - xhr.send();
85 - throw wakeable;
86 - }
87 - const result: Result = entry;
68 + },
69 + );
70 + return result;
71 +}
72 +
73 +function readResult(result: Result) {
74 if (result.status === Resolved) {
75 return result.value;
76 } else {
77 throw result.value;
78 }
79 }
80 +
81 +function Response(nativeResponse) {
82 + this.headers = nativeResponse.headers;
83 + this.ok = nativeResponse.ok;
84 + this.redirected = nativeResponse.redirected;
85 + this.status = nativeResponse.status;
86 + this.statusText = nativeResponse.statusText;
87 + this.type = nativeResponse.type;
88 + this.url = nativeResponse.url;
89 +
90 + this._response = nativeResponse;
91 + this._arrayBuffer = null;
92 + this._blob = null;
93 + this._json = null;
94 + this._text = null;
95 +}
96 +
97 +Response.prototype = {
98 + constructor: Response,
99 + arrayBuffer() {
100 + return readResult(
101 + this._arrayBuffer ||
102 + (this._arrayBuffer = toResult(this._response.arrayBuffer())),
103 + );
104 + },
105 + blob() {
106 + return readResult(
107 + this._blob || (this._blob = toResult(this._response.blob())),
108 + );
109 + },
110 + json() {
111 + return readResult(
112 + this._json || (this._json = toResult(this._response.json())),
113 + );
114 + },
115 + text() {
116 + return readResult(
117 + this._text || (this._text = toResult(this._response.text())),
118 + );
119 + },
120 +};
121 +
122 +export function fetch(url: string, options: mixed): Object {
123 + const map = readResultMap();
124 + let entry = map.get(url);
125 + if (!entry) {
126 + if (options) {
127 + if (options.method || options.body || options.signal) {
128 + // TODO: wire up our own cancellation mechanism.
129 + // TODO: figure out what to do with POST.
130 + throw Error('Unsupported option');
131 + }
132 + }
133 + const thenable = nativeFetch(url, options);
134 + entry = toResult(thenable);
135 + map.set(url, entry);
136 + }
137 + const nativeResponse = (readResult(entry): any);
138 + if (nativeResponse._reactResponse) {
139 + return nativeResponse._reactResponse;
140 + } else {
141 + return (nativeResponse._reactResponse = new Response(nativeResponse));
142 + }
143 +}