In version 1.0.1, when m.requests() is called with an incorrect URL, the promise returned stays indefinitely in the pending state.
Code to reproduce:
<!doctype html>
<html>
<body>
<script src="https://unpkg.com/mithril/mithril.js"></script>
<script>
console.log('Version', m.version);
console.log('Before request');
var p = m.request({
url: 'http://not-exists'
});
console.log('Request sent', p);
p.then(function(result) {
console.log('Promise resolved', result); // Never called
}).catch(function(error) {
console.log('Promise failed', error); // Never called
});
</script>
</body>
</html>
From @orbitbot :
the issue is that this line : https://github.com/lhorie/mithril.js/blob/next/request/request.js#L107 is outside of a try/catch block and leaves the promise hanging...
which seems to be where at least undefined URLs end up
Playing around with the sample code in FF & Chrome for a while, I've noted that
xhr.send, but xhr.onerror is fired with a ProgressEvent with type: "error" in both Chrome & FFopts argument similarly to headersm.request call, since it's not accessible in the config functionconfig methodm.request should arguably care a bit about the xhr.events by defaultxhr.open can actually also throw in some casesAll in all, this particular case could be fairly trivially fixed, but perhaps there is a reason to discuss if there are some reasonable differentiator between what m.request actually should handle as an API and what is left to the user, since functionality can be significantly different if Mithril attaches and tries to handle all the XHR event handlers by default.
Slightly edited test code: http://jsbin.com/penesegovu/edit?html,console
Here's a discussion on jQuery's solution a few years back https://bugs.jquery.com/ticket/14683
When a connection refused error occurs in Chrome, a stack trace is shown that makes it look like the exception happened synchronously from xhr.send()

However, you can't catch it by surrounding your call to m.request with a try catch block. xhr.onerror DOES get called. I suppose it would be the perfect world if that case causes the response Promise to be rejected. That screenshot is from:
@arienkock the reason you can't catch errors relating to high level HTTP failure is that it poses security risks, inasmuch as malicious code could determine a user's access to various services and resources.
As you point out, this is a browser feature — not something the library can address.
@barneycarroll Fair enough on the security issues (though admittedly I'm not entirely sure I grok the reasoning), but the returned promise being rejected if the request fails for any reason seems like the principle of least surprise to me. Axios and the Fetch API handle high level errors (that is errors not relating to the server's response) by rejecting the promise. https://jsbin.com/gehoboxuma/edit?js,console
IMO (with some afterthought, but there may be edge cases that I haven't considered), m.request should natively
xhr.onerror fires (if aborts end up here, then maybe that's one case when it doesnt?)xhr.ontimeout firesopts.timeout that just sets xhr.timeout = opts.timeoutopts.configWith the above, I'm not sure to what degree the implementation needs to care about readyState changes, or if there are _additional_ cases that require readyState observation for something or the other. @barneycarroll This is one example of where a Litmus project would be awesome for library writers...
The first two suggestions are not particularly difficult to implement, but already major version bump material. If someone for some reason feels that this is too much or this should rather be part of a larger release of multiple things, then at least exposing the promise would allow consumers to hook stuff like the other three suggestions in a minor version bump.
Disclaimer: just skimmed parts of the XHR2 spec, and haven't looked at the source of other XHR wrappers to see what cases they're handling or not.
@arienkock had posted a snippet to wrap m.request so that it rejects if xhr.onerror fires, I see it in my mail but not here... There was AFAICT a small bug, though (missing , reject as a then argument).
var m = require("mithril");
function createOptions(reject) {
return {
config: function(xhr) {
xhr.onerror = reject
}
}
}
module.exports = function(options) {
return new Promise(function(resolve, reject) {
m.request(Object.assign({}, options, createOptions(reject))).then(resolve, reject);
});
}
@pygy Hah, yes, I fixed that, but then I removed it after all as I suspected there was another bug in it. So far that version you posted seems to work with one caveat: It seems like m.redraw() needs to be called in any downstream rejection handlers.
Moving out of the 1.1.0 milestone since movement on this has slowed.
@orbitbot For those, here's my thoughts:
- reject the promise when xhr.onerror fires (if aborts end up here, then maybe that's one case when it doesnt?)
It seems odd that Mithril wouldn't reject here, to the point I'd consider it a bug.
- reject the promise when xhr.ontimeout fires
- support opts.timeout that just sets xhr.timeout = opts.timeout
I'd be okay with that if and only if opts.timeout is set. (If it's set via opts.config, let the user handle it.)
- minimally, expose the internal promise as f.e. the third parameter to opts.config
If by that, you mean resolve/reject, that could work for more advanced use cases. (If you need any post-request cleanup work, just use the return value.)
If by that, you mean resolve/reject, that could work for more advanced use cases.
Yeah, that was the intent. I keep forgetting that native promises didn't end up with the deferred API for some reason :smile:
Can't duplicate this in mithril@next, so closing.
https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4hcWrHxRaAcwAUAcgBCMMLQBOMAARaAjgFd4xRQEoA3AB00ANwwbtAB20BebVnz6jcYvODXtQO0DDShEbUUSYkdEAHpYtFpiAFoYAA8IHzhFa1YLa3FJGGk5JQAlGENjbTgRRQonfLRrR3xiQiZ5MAM0amIIenktOAMoYlNtfzQg7UKpGQVFAAUNWixMnWHJGxgAE3rdeFHx80D47QA5GB2HagwoWF3c03w74mpCLp6+gbR5GA0qw0EymMzmxQWShWaw22jAGGgewOAKBFjOsUu1wBs3uj2e5jYHBAmBweFecAENHojGYPDYAF0qFAIGgANYIFCcUk8dbtDTQAACaHSoioIXIPCijjgcViPUcrNkrzWsV5hH5UAFACZ8AAGPXJDTUfAARlVED50Hw-EoIGIAE9HNwQHBqPzHKJWPTWEA