index.js (5653B)
1 import {arrayBufferToString} from '~/lib/binary-strings'; 2 3 function successIfStatus200(xhr) { 4 return xhr.status >= 200 && xhr.status <= 299; 5 } 6 7 function retryIfNetworkErrorOrServerError(xhr) { 8 return (xhr.status === 0 || xhr.status >= 500); 9 } 10 11 class XMLHttpRequestPromiseError extends Error { 12 constructor(xhr) { 13 super(); 14 this.xhr = xhr; 15 this.name = 'XMLHttpRequestPromiseError'; 16 17 this.message = xhr.status === 0 ? 'network error' : `server response is ${xhr.status}`; 18 } 19 } 20 21 class XMLHttpRequestPromise { 22 constructor( 23 url, {method = 'GET', data = null, responseType = '', timeout = 30000, maxTries = 3, retryTimeWait = 500, 24 isResponseSuccess = successIfStatus200, responseNeedsRetry = retryIfNetworkErrorOrServerError, 25 headers = null, withCredentials = false} = {}) { 26 // console.log('promise constructor', url); 27 const promise = new Promise((resolve, reject) => { 28 this._resolve = resolve; 29 this._reject = reject; 30 } 31 ); 32 this.then = promise.then.bind(promise); 33 this.catch = promise.catch.bind(promise); 34 this.method = method; 35 this.url = url; 36 this.responseType = responseType; 37 this.postData = data; 38 this._isResponseSuccess = isResponseSuccess; 39 this._responseNeedsRetry = responseNeedsRetry; 40 this._retryTimeWait = retryTimeWait; 41 this.triesLeft = maxTries; 42 43 const xhr = this.xhr = new XMLHttpRequest(); 44 xhr.onreadystatechange = () => this._onreadystatechange(); 45 this._open(); 46 xhr.timeout = timeout; 47 if (responseType === 'binarystring') { 48 xhr.responseType = 'arraybuffer'; 49 } else { 50 xhr.responseType = responseType; 51 } 52 if (headers) { 53 for (let [k, v] of headers) { 54 xhr.setRequestHeader(k, v); 55 } 56 } 57 xhr.withCredentials = withCredentials; 58 } 59 60 _open() { 61 // console.log('open', this.url); 62 this.xhr.open(this.method, this.url); 63 } 64 65 _onreadystatechange() { 66 const xhr = this.xhr; 67 if (xhr.readyState === 4 && !this._aborted) { 68 // console.log('ready state 4', this.url); 69 xhr.responseBinaryText = ''; 70 if (this.responseType === 'binarystring' && xhr.response && xhr.response.byteLength) { 71 xhr.responseBinaryText = arrayBufferToString(xhr.response); 72 } 73 // IE doesnot support responseType=json 74 if (this.responseType === 'json' && (typeof xhr.response) === 'string') { 75 try { 76 // xhr.response is readonly 77 xhr.responseJSON = JSON.parse(xhr.response); 78 } catch (e) { 79 xhr.responseJSON = null; 80 } 81 } else { 82 xhr.responseJSON = xhr.response; 83 } 84 if (this._isResponseSuccess(xhr)) { 85 // console.log('success', this.url); 86 this._resolve(xhr); 87 } else { 88 if (this.triesLeft > 0 && this._responseNeedsRetry(xhr)) { 89 // console.log('retry', this.url); 90 this._open(); 91 this._timerId = setTimeout(() => this.send(), this._retryTimeWait); 92 } else { 93 // console.log('failed', this.url); 94 this._reject(new XMLHttpRequestPromiseError(xhr)); 95 } 96 } 97 } 98 } 99 100 abort() { 101 // console.log('abort', this.url); 102 this._aborted = true; 103 clearTimeout(this._timerId); 104 this.xhr.abort(); 105 } 106 107 send() { 108 // console.log('send', this.url); 109 this.triesLeft -= 1; 110 this.xhr.send(this.postData); 111 } 112 } 113 114 class XHRQueue { 115 constructor(maxSimultaneousRequests = 6) { 116 this._maxConnections = maxSimultaneousRequests; 117 this._queue = []; 118 this._activeCount = 0; 119 } 120 121 put(url, options) { 122 const promise = new XMLHttpRequestPromise(url, options); 123 promise._originalAbort = promise.abort; 124 promise.abort = () => this._abortPromise(promise); 125 this._queue.push(promise); 126 this._processQueue(); 127 return promise; 128 } 129 130 _abortPromise(promise) { 131 const i = this._queue.indexOf(promise); 132 if (i > -1) { 133 // console.log('ABORT IN QUEUE'); 134 this._queue.splice(i, 1); 135 } else { 136 if (promise.xhr.readyState === 4) { 137 // console.log('ABORT COMPLETED'); 138 } else { 139 // console.log('ABORT ACTIVE'); 140 promise._originalAbort(); 141 this._activeCount -= 1; 142 setTimeout(() => this._processQueue(), 0); 143 } 144 } 145 } 146 147 _processQueue() { 148 if (this._activeCount >= this._maxConnections || this._queue.length === 0) { 149 return; 150 } 151 const promise = this._queue.shift(); 152 promise 153 .catch(() => { 154 // do not throw if XHR request fails 155 }) 156 .then(() => this._onRequestReady(promise)); 157 this._activeCount += 1; 158 promise.send(); 159 } 160 161 _onRequestReady(promise) { 162 if (!promise._aborted) { 163 this._activeCount -= 1; 164 } 165 setTimeout(() => this._processQueue(), 0); 166 } 167 } 168 169 function fetch(url, options) { 170 // console.log('fetch', url); 171 const promise = new XMLHttpRequestPromise(url, options); 172 promise.send(); 173 return promise; 174 } 175 176 export {fetch, XHRQueue}; 177