Fossil SCM
Internal doc updates in fossil.fetch(). Ensure that fossil.fetch()'s onerror()/ontimeout() handler do not propagate exceptions (a defensive measure, not a fix for a known bug).
Commit
1d3db5050fead708460733621a9860dcdad8352ec987172dd9cf62485f1212d0
Parent
ee2a71b5c7e0aae…
1 file changed
+61
-29
+61
-29
| --- src/fossil.fetch.js | ||
| +++ src/fossil.fetch.js | ||
| @@ -27,36 +27,51 @@ | ||
| 27 | 27 | "this", noting that this call may have amended the options object |
| 28 | 28 | with state other than what the caller provided. |
| 29 | 29 | |
| 30 | 30 | - onerror: callback(Error object) (default = output error message |
| 31 | 31 | to console.error() and fossil.error()). Triggered if the request |
| 32 | - generates any response other than HTTP 200, suffers a connection | |
| 33 | - error or timeout while awaiting a response, or if the onload() | |
| 34 | - handler throws an exception. In the context of the callback, the | |
| 35 | - options object is "this". Note that this function is intended to be | |
| 36 | - used solely for error reporting, not error recovery. Because | |
| 37 | - onerror() may be called if onload() throws, it is up to the caller | |
| 38 | - to ensure that their onerror() callback references only state which | |
| 39 | - is valid in such a case. Special cases for the Error object: (1) If | |
| 40 | - the connection times out via XHR.ontimeout(), the error object will | |
| 41 | - have its (.name='timeout', .status=XHR.status) set. (2) Else if it | |
| 42 | - gets a non 2xx HTTP code then it will have | |
| 43 | - (.name='http',.status=XHR.status). (3) If it was proxied through a | |
| 44 | - JSON-format exception on the server, it will have | |
| 45 | - (.name='json',status=XHR.status). | |
| 32 | + generates any response other than HTTP 200, or if the beforesend() | |
| 33 | + or onload() handler throws an exception. In the context of the | |
| 34 | + callback, the options object is "this". This function is intended | |
| 35 | + to be used solely for error reporting, not error recovery. Special | |
| 36 | + cases for the Error object: | |
| 37 | + | |
| 38 | + 1. Timeouts unfortunately show up as a series of 2 events: an | |
| 39 | + HTTP 0 followed immediately by an XHR.ontimeout(). The former | |
| 40 | + cannot(?) be unambiguously identified as the trigger for the | |
| 41 | + pending timeout, so we have no option but to pass it on as-is | |
| 42 | + instead of flagging it as a timeout response. The latter will | |
| 43 | + trigger the client-provided ontimeout() if it's available (see | |
| 44 | + below), else it calls the onerror() callback. An error object | |
| 45 | + passed to ontimeout() by fetch() will have (.name='timeout', | |
| 46 | + .status=XHR.status). | |
| 47 | + | |
| 48 | + 2. Else if the response contains a JSON-format exception on the | |
| 49 | + server, it will have (.name='json-error', | |
| 50 | + status=XHR.status). Any JSON-format result object which has a | |
| 51 | + property named "error" is considered to be a server-generated | |
| 52 | + error. | |
| 53 | + | |
| 54 | + 3. Else if it gets a non 2xx HTTP code then it will have | |
| 55 | + (.name='http',.status=XHR.status). | |
| 56 | + | |
| 57 | + 4. If onerror() throws, the exception is suppressed but may | |
| 58 | + generate a console error message. | |
| 46 | 59 | |
| 47 | 60 | - ontimeout: callback(Error object). If set, timeout errors are |
| 48 | 61 | reported here, else they are reported through onerror(). |
| 49 | 62 | Unfortunately, XHR fires two events for a timeout: an |
| 50 | 63 | onreadystatechange() and an ontimeout(), in that order. From the |
| 51 | 64 | former, however, we cannot unambiguously identify the error as |
| 52 | 65 | having been caused by a timeout, so clients which set ontimeout() |
| 53 | - will get _two_ callback calls: one with noting HTTP 0 response | |
| 66 | + will get _two_ callback calls: one with with an HTTP error response | |
| 54 | 67 | followed immediately by an ontimeout() response. Error objects |
| 55 | - thown passed to this will have (.name='timeout') and | |
| 56 | - (.status=xhr.HttpStatus). In the context of the callback, the | |
| 57 | - options object is "this", | |
| 68 | + passed to this will have (.name='timeout', .status=xhr.HttpStatus). | |
| 69 | + In the context of the callback, the options object is "this", Like | |
| 70 | + onerror(), any exceptions thrown by the ontimeout() handler are | |
| 71 | + suppressed, but may generate a console error message. The onerror() | |
| 72 | + handler is _not_ called in this case. | |
| 58 | 73 | |
| 59 | 74 | - method: 'POST' | 'GET' (default = 'GET'). CASE SENSITIVE! |
| 60 | 75 | |
| 61 | 76 | - payload: anything acceptable by XHR2.send(ARG) (DOMString, |
| 62 | 77 | Document, FormData, Blob, File, ArrayBuffer), or a plain object or |
| @@ -64,11 +79,12 @@ | ||
| 64 | 79 | then the method is automatically set to 'POST'. By default XHR2 |
| 65 | 80 | will set the content type based on the payload type. If an |
| 66 | 81 | object/array is converted to JSON, the contentType option is |
| 67 | 82 | automatically set to 'application/json', and if JSON.stringify() of |
| 68 | 83 | that value fails then the exception is propagated to this |
| 69 | - function's caller. | |
| 84 | + function's caller. (beforesend(), aftersend(), and onerror() are | |
| 85 | + NOT triggered in that case.) | |
| 70 | 86 | |
| 71 | 87 | - contentType: Optional request content type when POSTing. Ignored |
| 72 | 88 | if the method is not 'POST'. |
| 73 | 89 | |
| 74 | 90 | - responseType: optional string. One of ("text", "arraybuffer", |
| @@ -76,10 +92,13 @@ | ||
| 76 | 92 | As an extension, it supports "json", which tells it that the |
| 77 | 93 | response is expected to be text and that it should be JSON.parse()d |
| 78 | 94 | before passing it on to the onload() callback. If parsing of such |
| 79 | 95 | an object fails, the onload callback is not called, and the |
| 80 | 96 | onerror() callback is passed the exception from the parsing error. |
| 97 | + If the parsed JSON object has an "error" property, it is assumed to | |
| 98 | + be an error string, which is used to populate a new Error object, | |
| 99 | + which will gets (.name="json") set on it. | |
| 81 | 100 | |
| 82 | 101 | - urlParams: string|object. If a string, it is assumed to be a |
| 83 | 102 | URI-encoded list of params in the form "key1=val1&key2=val2...", |
| 84 | 103 | with NO leading '?'. If it is an object, all of its properties get |
| 85 | 104 | converted to that form. Either way, the parameters get appended to |
| @@ -91,11 +110,11 @@ | ||
| 91 | 110 | value of that single header. If it's an array, it's treated as a |
| 92 | 111 | list of headers to return, and the 2nd argument is a map of those |
| 93 | 112 | header values. When a map is passed on, all of its keys are |
| 94 | 113 | lower-cased. When a given header is requested and that header is |
| 95 | 114 | set multiple times, their values are (per the XHR docs) |
| 96 | - concatenated together with ", " between them. | |
| 115 | + concatenated together with "," between them. | |
| 97 | 116 | |
| 98 | 117 | - beforesend/aftersend: optional callbacks which are called |
| 99 | 118 | without arguments immediately before the request is submitted |
| 100 | 119 | and immediately after it is received, regardless of success or |
| 101 | 120 | error. In the context of the callback, the options object is |
| @@ -151,11 +170,11 @@ | ||
| 151 | 170 | }); |
| 152 | 171 | return rc; |
| 153 | 172 | }; |
| 154 | 173 | } |
| 155 | 174 | if('/'===uri[0]) uri = uri.substr(1); |
| 156 | - if(!opt) opt = {}; | |
| 175 | + if(!opt) opt = {}/* should arguably be Object.create(null) */; | |
| 157 | 176 | else if('function'===typeof opt) opt={onload:opt}; |
| 158 | 177 | if(!opt.onload) opt.onload = f.onload; |
| 159 | 178 | if(!opt.onerror) opt.onerror = f.onerror; |
| 160 | 179 | if(!opt.beforesend) opt.beforesend = f.beforesend; |
| 161 | 180 | if(!opt.aftersend) opt.aftersend = f.aftersend; |
| @@ -188,14 +207,28 @@ | ||
| 188 | 207 | try{opt.aftersend()}catch(e){/*ignore*/} |
| 189 | 208 | const err = new Error("XHR timeout of "+x.timeout+"ms expired."); |
| 190 | 209 | err.status = x.status; |
| 191 | 210 | err.name = 'timeout'; |
| 192 | 211 | //console.warn("fetch.ontimeout",ev); |
| 193 | - (opt.ontimeout || opt.onerror)(err); | |
| 212 | + try{ | |
| 213 | + (opt.ontimeout || opt.onerror)(err); | |
| 214 | + }catch(e){ | |
| 215 | + /*ignore*/ | |
| 216 | + console.error("fossil.fetch()'s ontimeout() handler threw",e); | |
| 217 | + } | |
| 218 | + }; | |
| 219 | + /* Ensure that if onerror() throws, it's ignored. */ | |
| 220 | + const origOnError = opt.onerror; | |
| 221 | + opt.onerror = (arg)=>{ | |
| 222 | + try{ origOnError.call(this, arg) } | |
| 223 | + catch(e){ | |
| 224 | + /*ignored*/ | |
| 225 | + console.error("fossil.fetch()'s onerror() threw",e); | |
| 226 | + } | |
| 194 | 227 | }; |
| 195 | 228 | x.onreadystatechange = function(ev){ |
| 196 | - //console.warn("onreadystatechange", ev.target); | |
| 229 | + //console.warn("onreadystatechange", x.readyState, ev.target.responseText); | |
| 197 | 230 | if(XMLHttpRequest.DONE !== x.readyState) return; |
| 198 | 231 | try{opt.aftersend()}catch(e){/*ignore*/} |
| 199 | 232 | if(false && 0===x.status){ |
| 200 | 233 | /* For reasons unknown, we _sometimes_ trigger x.status==0 in FF |
| 201 | 234 | when the /chat page starts up, but not in Chrome nor in other |
| @@ -249,24 +282,23 @@ | ||
| 249 | 282 | try{ |
| 250 | 283 | const args = [(jsonResponse && x.response) |
| 251 | 284 | ? JSON.parse(x.response) : x.response]; |
| 252 | 285 | if(head) args.push(head); |
| 253 | 286 | opt.onload.apply(opt, args); |
| 254 | - }catch(e){ | |
| 255 | - opt.onerror(e); | |
| 287 | + }catch(err){ | |
| 288 | + opt.onerror(err); | |
| 256 | 289 | } |
| 257 | - }; | |
| 290 | + }/*onreadystatechange()*/; | |
| 258 | 291 | try{opt.beforesend()} |
| 259 | - catch(e){ | |
| 260 | - opt.onerror(e); | |
| 292 | + catch(err){ | |
| 293 | + opt.onerror(err); | |
| 261 | 294 | return; |
| 262 | 295 | } |
| 263 | 296 | x.open(opt.method||'GET', url.join(''), true); |
| 264 | 297 | if('POST'===opt.method && 'string'===typeof opt.contentType){ |
| 265 | 298 | x.setRequestHeader('Content-Type',opt.contentType); |
| 266 | 299 | } |
| 267 | - x.hasExplicitTimeout = !!(+opt.timeout); | |
| 268 | 300 | x.timeout = +opt.timeout || f.timeout; |
| 269 | 301 | if(undefined!==payload) x.send(payload); |
| 270 | 302 | else x.send(); |
| 271 | 303 | return this; |
| 272 | 304 | }; |
| 273 | 305 |
| --- src/fossil.fetch.js | |
| +++ src/fossil.fetch.js | |
| @@ -27,36 +27,51 @@ | |
| 27 | "this", noting that this call may have amended the options object |
| 28 | with state other than what the caller provided. |
| 29 | |
| 30 | - onerror: callback(Error object) (default = output error message |
| 31 | to console.error() and fossil.error()). Triggered if the request |
| 32 | generates any response other than HTTP 200, suffers a connection |
| 33 | error or timeout while awaiting a response, or if the onload() |
| 34 | handler throws an exception. In the context of the callback, the |
| 35 | options object is "this". Note that this function is intended to be |
| 36 | used solely for error reporting, not error recovery. Because |
| 37 | onerror() may be called if onload() throws, it is up to the caller |
| 38 | to ensure that their onerror() callback references only state which |
| 39 | is valid in such a case. Special cases for the Error object: (1) If |
| 40 | the connection times out via XHR.ontimeout(), the error object will |
| 41 | have its (.name='timeout', .status=XHR.status) set. (2) Else if it |
| 42 | gets a non 2xx HTTP code then it will have |
| 43 | (.name='http',.status=XHR.status). (3) If it was proxied through a |
| 44 | JSON-format exception on the server, it will have |
| 45 | (.name='json',status=XHR.status). |
| 46 | |
| 47 | - ontimeout: callback(Error object). If set, timeout errors are |
| 48 | reported here, else they are reported through onerror(). |
| 49 | Unfortunately, XHR fires two events for a timeout: an |
| 50 | onreadystatechange() and an ontimeout(), in that order. From the |
| 51 | former, however, we cannot unambiguously identify the error as |
| 52 | having been caused by a timeout, so clients which set ontimeout() |
| 53 | will get _two_ callback calls: one with noting HTTP 0 response |
| 54 | followed immediately by an ontimeout() response. Error objects |
| 55 | thown passed to this will have (.name='timeout') and |
| 56 | (.status=xhr.HttpStatus). In the context of the callback, the |
| 57 | options object is "this", |
| 58 | |
| 59 | - method: 'POST' | 'GET' (default = 'GET'). CASE SENSITIVE! |
| 60 | |
| 61 | - payload: anything acceptable by XHR2.send(ARG) (DOMString, |
| 62 | Document, FormData, Blob, File, ArrayBuffer), or a plain object or |
| @@ -64,11 +79,12 @@ | |
| 64 | then the method is automatically set to 'POST'. By default XHR2 |
| 65 | will set the content type based on the payload type. If an |
| 66 | object/array is converted to JSON, the contentType option is |
| 67 | automatically set to 'application/json', and if JSON.stringify() of |
| 68 | that value fails then the exception is propagated to this |
| 69 | function's caller. |
| 70 | |
| 71 | - contentType: Optional request content type when POSTing. Ignored |
| 72 | if the method is not 'POST'. |
| 73 | |
| 74 | - responseType: optional string. One of ("text", "arraybuffer", |
| @@ -76,10 +92,13 @@ | |
| 76 | As an extension, it supports "json", which tells it that the |
| 77 | response is expected to be text and that it should be JSON.parse()d |
| 78 | before passing it on to the onload() callback. If parsing of such |
| 79 | an object fails, the onload callback is not called, and the |
| 80 | onerror() callback is passed the exception from the parsing error. |
| 81 | |
| 82 | - urlParams: string|object. If a string, it is assumed to be a |
| 83 | URI-encoded list of params in the form "key1=val1&key2=val2...", |
| 84 | with NO leading '?'. If it is an object, all of its properties get |
| 85 | converted to that form. Either way, the parameters get appended to |
| @@ -91,11 +110,11 @@ | |
| 91 | value of that single header. If it's an array, it's treated as a |
| 92 | list of headers to return, and the 2nd argument is a map of those |
| 93 | header values. When a map is passed on, all of its keys are |
| 94 | lower-cased. When a given header is requested and that header is |
| 95 | set multiple times, their values are (per the XHR docs) |
| 96 | concatenated together with ", " between them. |
| 97 | |
| 98 | - beforesend/aftersend: optional callbacks which are called |
| 99 | without arguments immediately before the request is submitted |
| 100 | and immediately after it is received, regardless of success or |
| 101 | error. In the context of the callback, the options object is |
| @@ -151,11 +170,11 @@ | |
| 151 | }); |
| 152 | return rc; |
| 153 | }; |
| 154 | } |
| 155 | if('/'===uri[0]) uri = uri.substr(1); |
| 156 | if(!opt) opt = {}; |
| 157 | else if('function'===typeof opt) opt={onload:opt}; |
| 158 | if(!opt.onload) opt.onload = f.onload; |
| 159 | if(!opt.onerror) opt.onerror = f.onerror; |
| 160 | if(!opt.beforesend) opt.beforesend = f.beforesend; |
| 161 | if(!opt.aftersend) opt.aftersend = f.aftersend; |
| @@ -188,14 +207,28 @@ | |
| 188 | try{opt.aftersend()}catch(e){/*ignore*/} |
| 189 | const err = new Error("XHR timeout of "+x.timeout+"ms expired."); |
| 190 | err.status = x.status; |
| 191 | err.name = 'timeout'; |
| 192 | //console.warn("fetch.ontimeout",ev); |
| 193 | (opt.ontimeout || opt.onerror)(err); |
| 194 | }; |
| 195 | x.onreadystatechange = function(ev){ |
| 196 | //console.warn("onreadystatechange", ev.target); |
| 197 | if(XMLHttpRequest.DONE !== x.readyState) return; |
| 198 | try{opt.aftersend()}catch(e){/*ignore*/} |
| 199 | if(false && 0===x.status){ |
| 200 | /* For reasons unknown, we _sometimes_ trigger x.status==0 in FF |
| 201 | when the /chat page starts up, but not in Chrome nor in other |
| @@ -249,24 +282,23 @@ | |
| 249 | try{ |
| 250 | const args = [(jsonResponse && x.response) |
| 251 | ? JSON.parse(x.response) : x.response]; |
| 252 | if(head) args.push(head); |
| 253 | opt.onload.apply(opt, args); |
| 254 | }catch(e){ |
| 255 | opt.onerror(e); |
| 256 | } |
| 257 | }; |
| 258 | try{opt.beforesend()} |
| 259 | catch(e){ |
| 260 | opt.onerror(e); |
| 261 | return; |
| 262 | } |
| 263 | x.open(opt.method||'GET', url.join(''), true); |
| 264 | if('POST'===opt.method && 'string'===typeof opt.contentType){ |
| 265 | x.setRequestHeader('Content-Type',opt.contentType); |
| 266 | } |
| 267 | x.hasExplicitTimeout = !!(+opt.timeout); |
| 268 | x.timeout = +opt.timeout || f.timeout; |
| 269 | if(undefined!==payload) x.send(payload); |
| 270 | else x.send(); |
| 271 | return this; |
| 272 | }; |
| 273 |
| --- src/fossil.fetch.js | |
| +++ src/fossil.fetch.js | |
| @@ -27,36 +27,51 @@ | |
| 27 | "this", noting that this call may have amended the options object |
| 28 | with state other than what the caller provided. |
| 29 | |
| 30 | - onerror: callback(Error object) (default = output error message |
| 31 | to console.error() and fossil.error()). Triggered if the request |
| 32 | generates any response other than HTTP 200, or if the beforesend() |
| 33 | or onload() handler throws an exception. In the context of the |
| 34 | callback, the options object is "this". This function is intended |
| 35 | to be used solely for error reporting, not error recovery. Special |
| 36 | cases for the Error object: |
| 37 | |
| 38 | 1. Timeouts unfortunately show up as a series of 2 events: an |
| 39 | HTTP 0 followed immediately by an XHR.ontimeout(). The former |
| 40 | cannot(?) be unambiguously identified as the trigger for the |
| 41 | pending timeout, so we have no option but to pass it on as-is |
| 42 | instead of flagging it as a timeout response. The latter will |
| 43 | trigger the client-provided ontimeout() if it's available (see |
| 44 | below), else it calls the onerror() callback. An error object |
| 45 | passed to ontimeout() by fetch() will have (.name='timeout', |
| 46 | .status=XHR.status). |
| 47 | |
| 48 | 2. Else if the response contains a JSON-format exception on the |
| 49 | server, it will have (.name='json-error', |
| 50 | status=XHR.status). Any JSON-format result object which has a |
| 51 | property named "error" is considered to be a server-generated |
| 52 | error. |
| 53 | |
| 54 | 3. Else if it gets a non 2xx HTTP code then it will have |
| 55 | (.name='http',.status=XHR.status). |
| 56 | |
| 57 | 4. If onerror() throws, the exception is suppressed but may |
| 58 | generate a console error message. |
| 59 | |
| 60 | - ontimeout: callback(Error object). If set, timeout errors are |
| 61 | reported here, else they are reported through onerror(). |
| 62 | Unfortunately, XHR fires two events for a timeout: an |
| 63 | onreadystatechange() and an ontimeout(), in that order. From the |
| 64 | former, however, we cannot unambiguously identify the error as |
| 65 | having been caused by a timeout, so clients which set ontimeout() |
| 66 | will get _two_ callback calls: one with with an HTTP error response |
| 67 | followed immediately by an ontimeout() response. Error objects |
| 68 | passed to this will have (.name='timeout', .status=xhr.HttpStatus). |
| 69 | In the context of the callback, the options object is "this", Like |
| 70 | onerror(), any exceptions thrown by the ontimeout() handler are |
| 71 | suppressed, but may generate a console error message. The onerror() |
| 72 | handler is _not_ called in this case. |
| 73 | |
| 74 | - method: 'POST' | 'GET' (default = 'GET'). CASE SENSITIVE! |
| 75 | |
| 76 | - payload: anything acceptable by XHR2.send(ARG) (DOMString, |
| 77 | Document, FormData, Blob, File, ArrayBuffer), or a plain object or |
| @@ -64,11 +79,12 @@ | |
| 79 | then the method is automatically set to 'POST'. By default XHR2 |
| 80 | will set the content type based on the payload type. If an |
| 81 | object/array is converted to JSON, the contentType option is |
| 82 | automatically set to 'application/json', and if JSON.stringify() of |
| 83 | that value fails then the exception is propagated to this |
| 84 | function's caller. (beforesend(), aftersend(), and onerror() are |
| 85 | NOT triggered in that case.) |
| 86 | |
| 87 | - contentType: Optional request content type when POSTing. Ignored |
| 88 | if the method is not 'POST'. |
| 89 | |
| 90 | - responseType: optional string. One of ("text", "arraybuffer", |
| @@ -76,10 +92,13 @@ | |
| 92 | As an extension, it supports "json", which tells it that the |
| 93 | response is expected to be text and that it should be JSON.parse()d |
| 94 | before passing it on to the onload() callback. If parsing of such |
| 95 | an object fails, the onload callback is not called, and the |
| 96 | onerror() callback is passed the exception from the parsing error. |
| 97 | If the parsed JSON object has an "error" property, it is assumed to |
| 98 | be an error string, which is used to populate a new Error object, |
| 99 | which will gets (.name="json") set on it. |
| 100 | |
| 101 | - urlParams: string|object. If a string, it is assumed to be a |
| 102 | URI-encoded list of params in the form "key1=val1&key2=val2...", |
| 103 | with NO leading '?'. If it is an object, all of its properties get |
| 104 | converted to that form. Either way, the parameters get appended to |
| @@ -91,11 +110,11 @@ | |
| 110 | value of that single header. If it's an array, it's treated as a |
| 111 | list of headers to return, and the 2nd argument is a map of those |
| 112 | header values. When a map is passed on, all of its keys are |
| 113 | lower-cased. When a given header is requested and that header is |
| 114 | set multiple times, their values are (per the XHR docs) |
| 115 | concatenated together with "," between them. |
| 116 | |
| 117 | - beforesend/aftersend: optional callbacks which are called |
| 118 | without arguments immediately before the request is submitted |
| 119 | and immediately after it is received, regardless of success or |
| 120 | error. In the context of the callback, the options object is |
| @@ -151,11 +170,11 @@ | |
| 170 | }); |
| 171 | return rc; |
| 172 | }; |
| 173 | } |
| 174 | if('/'===uri[0]) uri = uri.substr(1); |
| 175 | if(!opt) opt = {}/* should arguably be Object.create(null) */; |
| 176 | else if('function'===typeof opt) opt={onload:opt}; |
| 177 | if(!opt.onload) opt.onload = f.onload; |
| 178 | if(!opt.onerror) opt.onerror = f.onerror; |
| 179 | if(!opt.beforesend) opt.beforesend = f.beforesend; |
| 180 | if(!opt.aftersend) opt.aftersend = f.aftersend; |
| @@ -188,14 +207,28 @@ | |
| 207 | try{opt.aftersend()}catch(e){/*ignore*/} |
| 208 | const err = new Error("XHR timeout of "+x.timeout+"ms expired."); |
| 209 | err.status = x.status; |
| 210 | err.name = 'timeout'; |
| 211 | //console.warn("fetch.ontimeout",ev); |
| 212 | try{ |
| 213 | (opt.ontimeout || opt.onerror)(err); |
| 214 | }catch(e){ |
| 215 | /*ignore*/ |
| 216 | console.error("fossil.fetch()'s ontimeout() handler threw",e); |
| 217 | } |
| 218 | }; |
| 219 | /* Ensure that if onerror() throws, it's ignored. */ |
| 220 | const origOnError = opt.onerror; |
| 221 | opt.onerror = (arg)=>{ |
| 222 | try{ origOnError.call(this, arg) } |
| 223 | catch(e){ |
| 224 | /*ignored*/ |
| 225 | console.error("fossil.fetch()'s onerror() threw",e); |
| 226 | } |
| 227 | }; |
| 228 | x.onreadystatechange = function(ev){ |
| 229 | //console.warn("onreadystatechange", x.readyState, ev.target.responseText); |
| 230 | if(XMLHttpRequest.DONE !== x.readyState) return; |
| 231 | try{opt.aftersend()}catch(e){/*ignore*/} |
| 232 | if(false && 0===x.status){ |
| 233 | /* For reasons unknown, we _sometimes_ trigger x.status==0 in FF |
| 234 | when the /chat page starts up, but not in Chrome nor in other |
| @@ -249,24 +282,23 @@ | |
| 282 | try{ |
| 283 | const args = [(jsonResponse && x.response) |
| 284 | ? JSON.parse(x.response) : x.response]; |
| 285 | if(head) args.push(head); |
| 286 | opt.onload.apply(opt, args); |
| 287 | }catch(err){ |
| 288 | opt.onerror(err); |
| 289 | } |
| 290 | }/*onreadystatechange()*/; |
| 291 | try{opt.beforesend()} |
| 292 | catch(err){ |
| 293 | opt.onerror(err); |
| 294 | return; |
| 295 | } |
| 296 | x.open(opt.method||'GET', url.join(''), true); |
| 297 | if('POST'===opt.method && 'string'===typeof opt.contentType){ |
| 298 | x.setRequestHeader('Content-Type',opt.contentType); |
| 299 | } |
| 300 | x.timeout = +opt.timeout || f.timeout; |
| 301 | if(undefined!==payload) x.send(payload); |
| 302 | else x.send(); |
| 303 | return this; |
| 304 | }; |
| 305 |