Skip to content

Instantly share code, notes, and snippets.

@alexeyraspopov

alexeyraspopov/request.js

Last active Sep 19, 2020
Embed
What would you like to do?
/* @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