Skip to content

Commit da8bc86

Browse files
spacejackpygy
authored andcommitted
feat: Don't reject m.request Promise if extract callback supplied (MithrilJS#2006)
1 parent 3ed6824 commit da8bc86

File tree

4 files changed

+36
-3
lines changed

4 files changed

+36
-3
lines changed

docs/change-log.md

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
- API: `m.redraw()` is always asynchronous ([#1592](https://github.com/MithrilJS/mithril.js/pull/1592))
2121
- API: `m.mount()` will only render its own root when called, it will not trigger a `redraw()` ([#1592](https://github.com/MithrilJS/mithril.js/pull/1592))
2222
- API: Assigning to `vnode.state` (as in `vnode.state = ...`) is no longer supported. Instead, an error is thrown if `vnode.state` changes upon the invocation of a lifecycle hook.
23+
- API: `m.request` will no longer reject the Promise on server errors (eg. status >= 400) if the caller supplies an `extract` callback. This gives applications more control over handling server responses.
2324

2425
#### News
2526

docs/request.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ Argument | Type | Required | Descr
5454
`options.type` | `any = Function(any)` | No | A constructor to be applied to each object in the response. Defaults to the [identity function](https://en.wikipedia.org/wiki/Identity_function).
5555
`options.serialize` | `string = Function(any)` | No | A serialization method to be applied to `data`. Defaults to `JSON.stringify`, or if `options.data` is an instance of [`FormData`](https://developer.mozilla.org/en/docs/Web/API/FormData), defaults to the [identity function](https://en.wikipedia.org/wiki/Identity_function) (i.e. `function(value) {return value}`).
5656
`options.deserialize` | `any = Function(string)` | No | A deserialization method to be applied to the `xhr.responseText`. Defaults to a small wrapper around `JSON.parse` that returns `null` for empty responses. If `extract` is defined, `deserialize` will be skipped.
57-
`options.extract` | `any = Function(xhr, options)` | No | A hook to specify how the XMLHttpRequest response should be read. Useful for processing response data, reading headers and cookies. By default this is a function that returns `xhr.responseText`, which is in turn passed to `deserialize`. If a custom `extract` callback is provided, the `xhr` parameter is the XMLHttpRequest instance used for the request, and `options` is the object that was passed to the `m.request` call. Additionally, `deserialize` will be skipped and the value returned from the extract callback will not automatically be parsed as JSON.
57+
`options.extract` | `any = Function(xhr, options)` | No | A hook to specify how the XMLHttpRequest response should be read. Useful for processing response data, reading headers and cookies. By default this is a function that returns `xhr.responseText`, which is in turn passed to `deserialize`. If a custom `extract` callback is provided, the `xhr` parameter is the XMLHttpRequest instance used for the request, and `options` is the object that was passed to the `m.request` call. Additionally, `deserialize` will be skipped and the value returned from the extract callback will be left as-is when the promise resolves. Furthermore, when an extract callback is provided, exceptions are *not* thrown when the server response status code indicates an error.
5858
`options.useBody` | `Boolean` | No | Force the use of the HTTP body section for `data` in `GET` requests when set to `true`, or the use of querystring for other HTTP methods when set to `false`. Defaults to `false` for `GET` requests and `true` for other methods.
5959
`options.background` | `Boolean` | No | If `false`, redraws mounted components upon completion of the request. If `true`, it does not. Defaults to `false`.
6060
**returns** | `Promise` | | A promise that resolves to the response data, after it has been piped through the `extract`, `deserialize` and `type` methods
@@ -81,6 +81,8 @@ A call to `m.request` returns a [promise](promise.md) and triggers a redraw upon
8181

8282
By default, `m.request` assumes the response is in JSON format and parses it into a Javascript object (or array).
8383

84+
If the HTTP response status code indicates an error, the returned Promise will be rejected. Supplying an extract callback will prevent the promise rejection.
85+
8486
---
8587

8688
### Typical usage
@@ -426,7 +428,7 @@ m.request({
426428

427429
### Retrieving response details
428430

429-
By default Mithril attempts to parse a response as JSON and returns `xhr.responseText`. It may be useful to inspect a server response in more detail, this can be accomplished by passing a custom `options.extract` function:
431+
By default Mithril attempts to parse `xhr.responseText` as JSON and returns the parsed object. It may be useful to inspect a server response in more detail and process it manually. This can be accomplished by passing a custom `options.extract` function:
430432

431433
```javascript
432434
m.request({

request/request.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ module.exports = function($window, Promise) {
8888
if (xhr.readyState === 4) {
8989
try {
9090
var response = (args.extract !== extract) ? args.extract(xhr, args) : args.deserialize(args.extract(xhr, args))
91-
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304 || FILE_PROTOCOL_REGEX.test(args.url)) {
91+
if (args.extract !== extract || (xhr.status >= 200 && xhr.status < 300) || xhr.status === 304 || FILE_PROTOCOL_REGEX.test(args.url)) {
9292
resolve(cast(args.type, response))
9393
}
9494
else {

request/tests/test-request.js

+30
Original file line numberDiff line numberDiff line change
@@ -519,5 +519,35 @@ o.spec("xhr", function() {
519519
o(e instanceof Error).equals(true)
520520
}).then(done)
521521
})
522+
o("does not reject on status error code when extract provided", function(done) {
523+
mock.$defineRoutes({
524+
"GET /item": function() {
525+
return {status: 500, responseText: JSON.stringify({message: "error"})}
526+
}
527+
})
528+
xhr({
529+
method: "GET", url: "/item",
530+
extract: function(xhr) {return JSON.parse(xhr.responseText)}
531+
}).then(function(data) {
532+
o(data.message).equals("error")
533+
done()
534+
})
535+
})
536+
o("rejects on error in extract", function(done) {
537+
mock.$defineRoutes({
538+
"GET /item": function() {
539+
return {status: 200, responseText: JSON.stringify({a: 1})}
540+
}
541+
})
542+
xhr({
543+
method: "GET", url: "/item",
544+
extract: function() {throw new Error("error")}
545+
}).catch(function(e) {
546+
o(e instanceof Error).equals(true)
547+
o(e.message).equals("error")
548+
}).then(function() {
549+
done()
550+
})
551+
})
522552
})
523553
})

0 commit comments

Comments
 (0)