170 lines
4.5 KiB
JavaScript
170 lines
4.5 KiB
JavaScript
/**
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @flow
|
|
*/
|
|
|
|
import type {Wakeable} from 'shared/ReactTypes';
|
|
|
|
import {unstable_getCacheForType} from 'react';
|
|
|
|
const Pending = 0;
|
|
const Resolved = 1;
|
|
const Rejected = 2;
|
|
|
|
type PendingRecord = {
|
|
status: 0,
|
|
value: Wakeable,
|
|
};
|
|
|
|
type ResolvedRecord = {
|
|
status: 1,
|
|
value: mixed,
|
|
};
|
|
|
|
type RejectedRecord = {
|
|
status: 2,
|
|
value: mixed,
|
|
};
|
|
|
|
type Record = PendingRecord | ResolvedRecord | RejectedRecord;
|
|
|
|
declare var globalThis: any;
|
|
|
|
// TODO: this is a browser-only version. Add a separate Node entry point.
|
|
const nativeFetch = (typeof globalThis !== 'undefined' ? globalThis : window)
|
|
.fetch;
|
|
|
|
function getRecordMap(): Map<string, Record> {
|
|
return unstable_getCacheForType(createRecordMap);
|
|
}
|
|
|
|
function createRecordMap(): Map<string, Record> {
|
|
return new Map();
|
|
}
|
|
|
|
function createRecordFromThenable(thenable): Record {
|
|
const record: Record = {
|
|
status: Pending,
|
|
value: thenable,
|
|
};
|
|
thenable.then(
|
|
value => {
|
|
if (record.status === Pending) {
|
|
const resolvedRecord = ((record: any): ResolvedRecord);
|
|
resolvedRecord.status = Resolved;
|
|
resolvedRecord.value = value;
|
|
}
|
|
},
|
|
err => {
|
|
if (record.status === Pending) {
|
|
const rejectedRecord = ((record: any): RejectedRecord);
|
|
rejectedRecord.status = Rejected;
|
|
rejectedRecord.value = err;
|
|
}
|
|
},
|
|
);
|
|
return record;
|
|
}
|
|
|
|
function readRecordValue(record: Record) {
|
|
if (record.status === Resolved) {
|
|
return record.value;
|
|
} else {
|
|
throw record.value;
|
|
}
|
|
}
|
|
|
|
function Response(nativeResponse) {
|
|
this.headers = nativeResponse.headers;
|
|
this.ok = nativeResponse.ok;
|
|
this.redirected = nativeResponse.redirected;
|
|
this.status = nativeResponse.status;
|
|
this.statusText = nativeResponse.statusText;
|
|
this.type = nativeResponse.type;
|
|
this.url = nativeResponse.url;
|
|
|
|
this._response = nativeResponse;
|
|
this._arrayBuffer = null;
|
|
this._blob = null;
|
|
this._json = null;
|
|
this._text = null;
|
|
}
|
|
|
|
Response.prototype = {
|
|
constructor: Response,
|
|
arrayBuffer() {
|
|
return readRecordValue(
|
|
// $FlowFixMe[object-this-reference] found when upgrading Flow
|
|
this._arrayBuffer ||
|
|
// $FlowFixMe[object-this-reference] found when upgrading Flow
|
|
(this._arrayBuffer = createRecordFromThenable(
|
|
// $FlowFixMe[object-this-reference] found when upgrading Flow
|
|
this._response.arrayBuffer(),
|
|
)),
|
|
);
|
|
},
|
|
blob() {
|
|
return readRecordValue(
|
|
// $FlowFixMe[object-this-reference] found when upgrading Flow
|
|
this._blob ||
|
|
// $FlowFixMe[object-this-reference] found when upgrading Flow
|
|
(this._blob = createRecordFromThenable(this._response.blob())),
|
|
);
|
|
},
|
|
json() {
|
|
return readRecordValue(
|
|
// $FlowFixMe[object-this-reference] found when upgrading Flow
|
|
this._json ||
|
|
// $FlowFixMe[object-this-reference] found when upgrading Flow
|
|
(this._json = createRecordFromThenable(this._response.json())),
|
|
);
|
|
},
|
|
text() {
|
|
return readRecordValue(
|
|
// $FlowFixMe[object-this-reference] found when upgrading Flow
|
|
this._text ||
|
|
// $FlowFixMe[object-this-reference] found when upgrading Flow
|
|
(this._text = createRecordFromThenable(this._response.text())),
|
|
);
|
|
},
|
|
};
|
|
|
|
function preloadRecord(url: string, options: mixed): Record {
|
|
const map = getRecordMap();
|
|
let record = map.get(url);
|
|
if (!record) {
|
|
if (options) {
|
|
if (options.method || options.body || options.signal) {
|
|
// TODO: wire up our own cancellation mechanism.
|
|
// TODO: figure out what to do with POST.
|
|
// eslint-disable-next-line react-internal/prod-error-codes
|
|
throw Error('Unsupported option');
|
|
}
|
|
}
|
|
const thenable = nativeFetch(url, options);
|
|
record = createRecordFromThenable(thenable);
|
|
map.set(url, record);
|
|
}
|
|
return record;
|
|
}
|
|
|
|
export function preload(url: string, options: mixed): void {
|
|
preloadRecord(url, options);
|
|
// Don't return anything.
|
|
}
|
|
|
|
export function fetch(url: string, options: mixed): Object {
|
|
const record = preloadRecord(url, options);
|
|
const nativeResponse = (readRecordValue(record): any);
|
|
if (nativeResponse._reactResponse) {
|
|
return nativeResponse._reactResponse;
|
|
} else {
|
|
// $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions
|
|
return (nativeResponse._reactResponse = new Response(nativeResponse));
|
|
}
|
|
}
|