Last active
October 13, 2023 15:10
-
-
Save westc/8f66419c328bac89acfd010965b38c8a to your computer and use it in GitHub Desktop.
Sends a request to the specified URL.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Sends a request to the specified URL. | |
* @param {string} url | |
* The URL to which the request will be sent. | |
* @param {?request__Options=} options | |
* The various options to set on the request. | |
* @returns {Promise<XMLHttpRequest>} | |
* A promise of the corresponding `XMLHttpRequest`. | |
*/ | |
function request(url, options) { | |
let {data, headers, method, onDone, onSetup, params, type} = options ?? {}; | |
return new Promise((resolve, reject) => { | |
const xhr = new XMLHttpRequest(); | |
// Capture when the request is done loading... | |
xhr.addEventListener("readystatechange", () => { | |
if(xhr.readyState === XMLHttpRequest.DONE) { | |
const success = 200 <= xhr.status && xhr.status < 400; | |
(success ? resolve : reject)(xhr); | |
onDone && onDone( | |
success, | |
xhr.responseXML ?? xhr.responseText ?? xhr.response, | |
xhr | |
); | |
} | |
}); | |
// If params were specified add them to the URL. | |
if (params) { | |
url = url.replace( | |
/\?([^#]*)|(?=#)|$/, | |
(_, qs) => { | |
const usp = new URLSearchParams(qs); | |
for (const [name, values] of Object.entries(params)) { | |
for (const value of Array.isArray(values) ? values : [values]) { | |
usp.append(name, value); | |
} | |
} | |
return `${usp}` && `?${usp}`; | |
} | |
); | |
} | |
// Open the URL using the specified method (defaults to "GET"). | |
xhr.open(method ?? "GET", url); | |
// Set the extra headers if they were specified. | |
if (headers) { | |
for (const [key, value] of Object.entries(headers)) { | |
xhr.setRequestHeader(key, value); | |
} | |
} | |
// Set the content type if specified. | |
if (type) { | |
// Convert the data for special types: form-data, form-urlencoded and | |
// JSON. | |
const [m, isJSON, isFormData] = /^(?:(?:application\/|text\/)?(json)|multipart\/(form-data)|application\/x-www-form-urlencoded)$/i.exec(type) ?? []; | |
if (m) { | |
if (isJSON) { | |
if ('string' === typeof data) { | |
try { | |
JSON.parse(data); | |
} | |
catch (e) { | |
data = JSON.stringify(data); | |
} | |
} | |
else { | |
try { | |
data = JSON.stringify(data); | |
} catch(e) {} | |
} | |
} | |
else if (data && 'object' === typeof data && !(data instanceof FormData)) { | |
const newContainer = isFormData ? new FormData() : new URLSearchParams(); | |
for (const [key, values] of Object.entries(data)) { | |
for (const value of (Array.isArray(values) ? values : [values])) { | |
newContainer.append(key, value); | |
} | |
} | |
data = isFormData ? newContainer : newContainer.toString(); | |
// Remove type if it will be automatically determined via the form | |
// data. | |
if (isFormData) type = false; | |
} | |
} | |
// Set the type to content-type unless it was removed. | |
if (type) xhr.setRequestHeader("Content-Type", type); | |
} | |
// If there is an onSetup() function defined go ahead and call it. | |
if (onSetup) onSetup(xhr); | |
// Send the request. | |
xhr.send(data ?? undefined); | |
}); | |
} | |
/** | |
* @typedef request__Options | |
* @property {?(FormData|string|Record<string,string|string[]>)=} data | |
* The data sent with the request. | |
* @property {?{[key: string]: string}=} headers | |
* The headers to set on the request. | |
* @property {?("GET"|"POST"|"PUT"|"PATCH"|"DELETE"|"HEAD"|"OPTIONS")=} method | |
* The method to use to make the request. | |
* @property {?((xhr: XMLHttpRequest) => void)=} onSetup | |
* The function that is run right before sending the request. | |
* @property {?((success: boolean, response: *, xhr: XMLHttpRequest) => void)=} onDone | |
* The function that is run once the request is done (whether or succeeded | |
* or failed). | |
* @property {?Record<string,string|string[]>=} params | |
* The URL parameters to add to any that were already specified in the given | |
* URL. | |
* @property {?string=} type | |
* The value of the "Content-Type" header. This will override the | |
* "Content-Type" header set in `options.headers` unless it is set to | |
* "multipart/form-data" in which case the content-type will be automatically | |
* generated based on the `data` passed in. | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment