/* @flow */ | |
/** | |
* @example Simple GET request | |
* let users = await request('/api/users'); | |
* | |
* @example Cancellable GET request | |
* let [result, abort] = request('/api/users', { cancellable: true }); | |
* | |
* @example Simple POST request | |
* let task = await request('/api/tasks', { | |
* method: 'POST', | |
* body: { title, description }, | |
* }); | |
* | |
* @example Cancellable POST request | |
* let [result, abort] = request('/api/tasks', { | |
* cancellable: true, | |
* method: 'POST', | |
* body: { title, description }, | |
* }); | |
*/ | |
// Default get request that returns a promise | |
declare function request<Payload>( | |
url: string, | |
options?: { | |
method?: 'GET', | |
headers?: { [string]: string } | Headers, | |
signal?: AbortSignal, | |
}, | |
): Promise<Payload>; | |
// Cancellable get request that returns a pair of promise and abort function | |
declare function request<Payload>( | |
url: string, | |
options?: { | |
method?: 'GET', | |
headers?: { [string]: string } | Headers, | |
cancellable: true, | |
}, | |
): [Promise<Payload>, () => void]; | |
// Default actionable request with body that returns a promise | |
declare function request<Payload>( | |
url: string, | |
options?: { | |
method: 'POST' | 'PATCH' | 'PUT', | |
body?: string | FormData | { ... }, | |
headers?: { [string]: string } | Headers, | |
signal?: AbortSignal, | |
}, | |
): Promise<Payload>; | |
// Cancellable actionable request with body that returns a pair of promise and abort function | |
declare function request<Payload>( | |
url: string, | |
options?: { | |
method: 'POST' | 'PATCH' | 'PUT', | |
body?: string | FormData | { ... }, | |
headers?: { [string]: string } | Headers, | |
cancellable: true, | |
}, | |
): [Promise<Payload>, () => void]; | |
// Default body-less request that returns a promise without any data | |
declare function request<Payload>( | |
url: string, | |
options?: { | |
method: 'HEAD' | 'DELETE', | |
headers?: { [string]: string } | Headers, | |
signal?: AbortSignal, | |
}, | |
): Promise<void>; | |
// Cancellable body-less request that returns a pair of promise without any data and abort function | |
declare function request<Payload>( | |
url: string, | |
options?: { | |
method: 'HEAD' | 'DELETE', | |
headers?: { [string]: string } | Headers, | |
cancellable: true, | |
}, | |
): [Promise<void>, () => void]; | |
export default function request(url, options = {}) { | |
let method = options.method ?? 'GET'; | |
let headers = options.headers ?? { 'Content-Type': 'application/json' }; | |
// In a case of body being an object, let's stringify it so the user don't need to | |
let body = | |
options.body instanceof FormData || typeof options.body === 'string' | |
? options.body | |
: options.body && JSON.stringify(options.body); | |
// Despite providing cancellable API, there are cases where we need special access to the controller | |
let signal = options.signal; | |
if (options.cancellable) { | |
let ctl = new AbortController(); | |
let signal = ctl.signal; | |
let response = makeRequest(url, { method, headers, body, signal }); | |
return [response, () => ctl.abort()]; | |
} else { | |
return makeRequest(url, { method, headers, body, signal }); | |
} | |
} | |
async function makeRequest(url, options) { | |
let response = await fetch(url, options); | |
// 4xx requests include a JSON payload that describes the error | |
if (response.status >= 400 && response.status < 500) { | |
let payload = await response.json(); | |
throw payload; | |
} | |
// Otherwise, fallback to status text | |
if (!response.ok) { | |
throw new Error(response.statusText); | |
} | |
if (response.status !== 204 && options.method !== 'HEAD') { | |
return response.json(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment