diff --git a/README.md b/README.md index b8fd4e2..33af55f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Oxhr v1.2.0 -An object-oriented XHR (XMLHttpRequest) wrapper/library. +An object-oriented and asynchronous XHR (XMLHttpRequest) wrapper. ### Modern programmers use fetch, others prefer Oxhr 🐮 @@ -7,18 +7,15 @@ An object-oriented XHR (XMLHttpRequest) wrapper/library. ### Why Oxhr? - Written in TypeScript -- No dependencies - Promise-based (asynchronous) +- Modular (JavaScript modules) - Tiny (under 5kb minified) -- Robust and simple +- No dependencies +- Robust and simple, useful for most tasks - An alternative to _XMLHttpRequest_ and _fetch_ -- An alternative to feature-rich libraries like [_axios_](https://github.com/axios/axios) - -A small demo is included, see `index.html`. +- An alternative to feature-rich and complex libraries like [_axios_](https://github.com/axios/axios) -If you are not familiar with ES modules have a look [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules). - -There is also an old version of the library called jXhr which was not written using OOP, see [here](https://github.com/Amarok24/Oxhr/tree/non-oop-version). +A small demo is included, see `index.html`. If you prefer a plain .js file instead of a modular system you can use a bundler to combine all modules into one file; for such a task I recommend [Deno](https://deno.land/) which has a bundle feature. --- ## API @@ -42,7 +39,7 @@ async function fetchDataExample() // Send request to the server and output response data to console. response = await myConnection.send(); - console.log(result); + console.log(response); } ``` @@ -57,11 +54,11 @@ interface IOxhrParams data?: XMLHttpRequestBodyInit | Document | null; requestHeaders?: IRequestHeader[]; timeoutMs?: number; - consoleInfo?: string; + consoleMessage?: string; debug?: boolean; onLoadEnd?: () => void; onProgress?: (percent: number, bytes: number) => void; - onTimeOut?: () => void; + onTimeout?: () => void; onAbort?: () => void; } ``` @@ -72,11 +69,11 @@ interface IOxhrParams | :------------- | :----------------------- | :-----: | :----: | :--- | | url | URL for http request | x | | string | | method | HTTP request method | - | "GET" | string ("GET" \| "POST" \| etc...)) | -| responseType | A valid response type | - | "" | "", "arraybuffer", "blob", "document", "json", "text" | +| responseType | One of response types | - | "" | "", "arraybuffer", "blob", "document", "json", "text" | | data | Data to send | - | null | Blob, BufferSource, FormData, URLSearchParams, ReadableStream, string, Document, null | | requestHeaders | Array of IRequestHeader | - | | IRequestHeader[] | -| timeoutMs | Timeout in milliseconds | - | 60'000 | number | -| consoleInfo | Description for console output after loading is done. | - | | string | +| timeoutMs | Timeout in milliseconds. If the request takes longer, it will be terminated. | - | 0 | number | +| consoleMessage | Description for console output after loading is done. | - | | string | | debug | Additional console output | - | false | boolean | @@ -86,10 +83,10 @@ interface IOxhrParams | :-- | :-- | :-- | | onLoadEnd | Called after success, timeout, abort or error | -- | | onProgress | Ongoing loading progress in percent and bytes | (percent: number, bytes: number) | -| onTimeOut | Time-out callback (see timeoutMs parameter) | -- | +| onTimeout | Timeout callback (see timeoutMs parameter) | -- | | onAbort | When an open connection gets aborted | -- | -Please note that progress in % must not always work because it depends on server settings (not all connections give you the total data/file size). +Please note that progress in % must not always work because it depends on server settings (not all connections give you the total data/file size in advance). ### Methods @@ -106,9 +103,9 @@ Please note that progress in % must not always work because it depends on server | instanceId | A unique ID of the Oxhr instance (UUID). | | readyState | The XHR readyState code. | | status | The XHR status code. | -| success | Returns _true_ if transfer was successful (readyState is DONE and status is OK). | +| success | Returns _true_ if transfer was successful (readyState is DONE and status is OK). | +| isProcessed | Returns _true_ if a request is already being processed. | -There is also a simple getter for reading the "readyState" of current XMLHttpRequest connection. ## FAQs @@ -121,13 +118,13 @@ __Can I open multiple connections at once?__ ## Changelog ### _v1.2.0_ +- Many code improvements, now the library is more robust and handles all kind of wrong usage in a nice way +- Added 4 new getters: instanceId, status, success, isProcessed +- Major code refactoring - Previous version didn't compile in TypeScript 4.5.4 because of an error, this update fixes it -- Added new "debug" parameter for more verbose console log - Several small bugfixes, one big bugfix for custom onLoadEnd method which didn't get triggered in TypeScript 4.5.4 -- Added 3 new getters: instanceId, status, success +- Added new "debug" parameter for more verbose console log - Demo in index.html changed significantly -- Many code improvements, now the library is more robust and handles all kind of wrong usage in a nice way -- Major code refactoring --- diff --git a/dist/demo.js b/dist/demo.js index 914417b..cacca99 100644 --- a/dist/demo.js +++ b/dist/demo.js @@ -1,7 +1,6 @@ import { Oxhr } from './oxhr.js'; import { ResourcesType } from './swapi-schema.js'; import { OxhrError } from './oxhr-error.js'; -import { XhrReadyState } from './xhr-codes.js'; const startButton = document.querySelector('#startButton'); const abortButton = document.querySelector('#abortButton'); const swButton = document.querySelector('#swButton'); @@ -12,7 +11,7 @@ async function fetchRandomStarWarsData() { const random = Math.floor(Math.random() * 10 + 1); const swOptions = { url: `https://swapi.dev/api/${ResourcesType.People}/${random}`, - consoleInfo: 'StarWars connection finished, some details below.', + consoleMessage: 'StarWars connection finished, some details below.', onLoadEnd: () => { console.info('Opening browser pop-up message with character data...'); alert(JSON.stringify(peopleResponse)); @@ -38,9 +37,9 @@ const myOptions = { requestHeaders: myRequestHeaders, timeoutMs: 20000, debug: true, - consoleInfo: 'My test connection finished, some details below.', + consoleMessage: 'My test connection finished, some details below.', onLoadEnd: onLoadEnd, - onTimeOut: onTimeOut, + onTimeout: onTimeout, onProgress: onProgress, onAbort: onAbort }; @@ -52,7 +51,7 @@ async function tryToSendData() { console.log('try-block of tryToSendData'); console.log(`myConnection.instanceId = ${myConnection.instanceId}`); console.log(`XHR status of myConnection is ${myConnection.status}`); - if ((myConnection.readyState !== XhrReadyState.DONE) && (myConnection.readyState !== XhrReadyState.UNSENT)) { + if (myConnection.isProcessed) { console.log('You have probably clicked on that button while a connection is being processed.'); return; } @@ -93,7 +92,7 @@ function onLoadEnd() { alert('Success!'); } } -function onTimeOut() { +function onTimeout() { console.log('demo: custom timeout handler'); alert('Connection time out!'); } diff --git a/dist/xhr-handler.js b/dist/xhr-handler.js index 278ae66..dbdc0b8 100644 --- a/dist/xhr-handler.js +++ b/dist/xhr-handler.js @@ -11,10 +11,10 @@ class XhrHandler { if (this.xhr.status >= XhrStatus.LOADING && this.xhr.status < XhrStatus.MULTIPLE_CHOICE) { resolve(this.xhr.response); - if (this.params.consoleInfo) - console.group(this.params.consoleInfo); + if (this.params.consoleMessage) + console.group(this.params.consoleMessage); console.log(`${ev.loaded} bytes loaded.`); - if (this.params.consoleInfo) + if (this.params.consoleMessage) console.groupEnd(); } else { @@ -24,11 +24,11 @@ class XhrHandler { const handleError = (ev) => { this.debugMessage(`handleError()`); reject(new OxhrError('Failed to send request!')); - if (this.params.consoleInfo) - console.group(this.params.consoleInfo); + if (this.params.consoleMessage) + console.group(this.params.consoleMessage); console.log(ev); console.error(`xhr status: ${this.xhr.status}`); - if (this.params.consoleInfo) + if (this.params.consoleMessage) console.groupEnd(); }; const handleProgress = (ev) => { @@ -54,8 +54,8 @@ class XhrHandler { this.xhr.removeEventListener('progress', handleProgress); if (this.params.onAbort) this.xhr.removeEventListener('abort', this.params.onAbort); - if (this.params.onTimeOut) { - this.xhr.removeEventListener('timeout', this.params.onTimeOut); + if (this.params.onTimeout) { + this.xhr.removeEventListener('timeout', this.params.onTimeout); } else { this.xhr.removeEventListener('timeout', handleTimeout); @@ -72,7 +72,7 @@ class XhrHandler { } }); } - this.xhr.timeout = this.params.timeoutMs ?? 60000; + this.xhr.timeout = this.params.timeoutMs ?? 0; this.xhr.responseType = this.responseType; if (!this._eventHandlersAssigned) { this._eventHandlersAssigned = true; @@ -83,8 +83,8 @@ class XhrHandler { this.xhr.addEventListener('progress', handleProgress); if (this.params.onAbort) this.xhr.addEventListener('abort', this.params.onAbort); - if (this.params.onTimeOut) { - this.xhr.addEventListener('timeout', this.params.onTimeOut); + if (this.params.onTimeout) { + this.xhr.addEventListener('timeout', this.params.onTimeout); } else { this.xhr.addEventListener('timeout', handleTimeout); @@ -114,4 +114,8 @@ class XhrHandler { return (this.xhr.readyState === XhrReadyState.DONE && this.xhr.status === XhrStatus.OK); } + get isProcessed() { + return (this.xhr.readyState !== XhrReadyState.DONE && + this.xhr.readyState !== XhrReadyState.UNSENT); + } } diff --git a/minify.sh b/minify.sh deleted file mode 100644 index 77d2a25..0000000 --- a/minify.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh -# echo $@ -echo "Minifying oxhr.js with Terser..." - -terser dist/oxhr.js --compress --mangle --output dist/oxhr.min.js diff --git a/src/demo.ts b/src/demo.ts index 44178d1..cc68c07 100644 --- a/src/demo.ts +++ b/src/demo.ts @@ -6,7 +6,6 @@ https://github.com/Amarok24/Oxhr import {Oxhr} from './oxhr.js'; import {IPeople, ResourcesType} from './swapi-schema.js'; import {OxhrError} from './oxhr-error.js'; -import {XhrReadyState} from './xhr-codes.js'; import type {IOxhrParams, IRequestHeader} from './oxhr-types.js'; const startButton = document.querySelector('#startButton'); @@ -26,7 +25,7 @@ async function fetchRandomStarWarsData(): Promise const swOptions: IOxhrParams = { // I also recommend this free API for testing: https://webhook.site/ url: `https://swapi.dev/api/${ ResourcesType.People }/${ random }`, - consoleInfo: 'StarWars connection finished, some details below.', + consoleMessage: 'StarWars connection finished, some details below.', onLoadEnd: () => { console.info('Opening browser pop-up message with character data...'); @@ -76,9 +75,9 @@ const myOptions: IOxhrParams = { requestHeaders: myRequestHeaders, timeoutMs: 20000, debug: true, - consoleInfo: 'My test connection finished, some details below.', + consoleMessage: 'My test connection finished, some details below.', onLoadEnd: onLoadEnd, - onTimeOut: onTimeOut, + onTimeout: onTimeout, onProgress: onProgress, onAbort: onAbort }; @@ -103,7 +102,7 @@ async function tryToSendData(): Promise console.log(`myConnection.instanceId = ${ myConnection.instanceId }`); console.log(`XHR status of myConnection is ${ myConnection.status }`); - if ((myConnection.readyState !== XhrReadyState.DONE) && (myConnection.readyState !== XhrReadyState.UNSENT)) + if (myConnection.isProcessed) { console.log('You have probably clicked on that button while a connection is being processed.'); return; @@ -164,7 +163,7 @@ function onLoadEnd(): void } } -function onTimeOut(): void +function onTimeout(): void { console.log('demo: custom timeout handler'); alert('Connection time out!'); diff --git a/src/oxhr-types.ts b/src/oxhr-types.ts index 34edce0..f6cbfdc 100644 --- a/src/oxhr-types.ts +++ b/src/oxhr-types.ts @@ -20,12 +20,12 @@ export type { * @param data Data to send with request. Supports all valid data types. Default: null. * @param responseType A valid response type. Default: "". * @param requestHeaders Custom HTTP headers. Array of IRequestHeader. - * @param timeoutMs Timeout in milliseconds after which connection will be interrupted. - * @param consoleInfo Description of console.group for console output. + * @param timeoutMs Timeout in milliseconds. After that time the request will be terminated. + * @param consoleMessage Description for console output after loading is done. * @param debug Output additional info to the browser console (debug mode). * @param onLoadEnd Called after load success, timeout, abort or error. * @param onProgress Callback function to which the loading progress in % shall be passed. - * @param onTimeOut Callback function for timeout. + * @param onTimeout The ionTimeout callback is fired when progression is terminated due to preset time expiring (see timeoutMs). * @param onAbort Callback function when an open connection is aborted. */ interface IOxhrParams @@ -36,11 +36,11 @@ interface IOxhrParams data?: CombinedDataType; requestHeaders?: IRequestHeader[]; timeoutMs?: number; - consoleInfo?: string; + consoleMessage?: string; debug?: boolean; onLoadEnd?: () => void; onProgress?: (percent: number, bytes: number) => void; - onTimeOut?: () => void; + onTimeout?: () => void; onAbort?: () => void; } diff --git a/src/xhr-handler.ts b/src/xhr-handler.ts index c1fe989..c8dc7dd 100644 --- a/src/xhr-handler.ts +++ b/src/xhr-handler.ts @@ -44,9 +44,9 @@ class XhrHandler this.xhr.status < XhrStatus.MULTIPLE_CHOICE) { resolve(this.xhr.response); - if (this.params.consoleInfo) console.group(this.params.consoleInfo); + if (this.params.consoleMessage) console.group(this.params.consoleMessage); console.log(`${ ev.loaded } bytes loaded.`); - if (this.params.consoleInfo) console.groupEnd(); + if (this.params.consoleMessage) console.groupEnd(); } else { @@ -63,10 +63,10 @@ class XhrHandler { this.debugMessage(`handleError()`); reject(new OxhrError('Failed to send request!')); - if (this.params.consoleInfo) console.group(this.params.consoleInfo); + if (this.params.consoleMessage) console.group(this.params.consoleMessage); console.log(ev); console.error(`xhr status: ${ this.xhr.status }`); - if (this.params.consoleInfo) console.groupEnd(); + if (this.params.consoleMessage) console.groupEnd(); }; const handleProgress = (ev: ProgressEvent): void => @@ -79,7 +79,7 @@ class XhrHandler } else { - // If ev.total is unknown then it is set to 0 automatically. + // If ev.total is unknown then it is set automatically to 0. if (this.params.onProgress) this.params.onProgress(-1, ev.loaded); } }; @@ -105,9 +105,9 @@ class XhrHandler //if (this.params.onLoadEnd) this.xhr.removeEventListener('loadend', this.params.onLoadEnd); if (this.params.onAbort) this.xhr.removeEventListener('abort', this.params.onAbort); - if (this.params.onTimeOut) + if (this.params.onTimeout) { - this.xhr.removeEventListener('timeout', this.params.onTimeOut); + this.xhr.removeEventListener('timeout', this.params.onTimeout); } else { @@ -134,9 +134,8 @@ class XhrHandler } // Timeout on client side differs from server timeout. Default timeout is 0 (never). - // If timeout > 0 specified then fetching data will be interrupted after given time - // and the "timeout" event and "loadend" events will be triggered. - this.xhr.timeout = this.params.timeoutMs ?? 60000; + // If timeout > 0 specified then fetching data will be interrupted after given time and the "timeout" event and "loadend" events will be triggered. + this.xhr.timeout = this.params.timeoutMs ?? 0; // If respType == "json" then XMLHttpRequest will perform JSON.parse(). this.xhr.responseType = this.responseType; @@ -155,9 +154,9 @@ class XhrHandler if (this.params.onAbort) this.xhr.addEventListener('abort', this.params.onAbort); - if (this.params.onTimeOut) + if (this.params.onTimeout) { - this.xhr.addEventListener('timeout', this.params.onTimeOut); + this.xhr.addEventListener('timeout', this.params.onTimeout); } else { @@ -208,6 +207,18 @@ class XhrHandler ); } + /** + * Returns true if a request is currently being processed. + * You may want to use it to avoid execution of a second await-Promise task on the same Oxhr instance. + */ + get isProcessed(): boolean + { + return ( + this.xhr.readyState !== XhrReadyState.DONE && + this.xhr.readyState !== XhrReadyState.UNSENT + ); + } + } // TODO: implement event handlers for processing uploads