-
-
Save mikeal/013dbb2ef3810cfe976cbe16ddb11c6f to your computer and use it in GitHub Desktop.
const r2 = require('r2') | |
let doJsonThing = async (path, propname) => { | |
let res = await r2(`http://api.com${path}`).json | |
return res[propname] | |
} |
const request = require('request') | |
let doJsonThing = (path, propname, cb) => { | |
request(`http://api.com${path}`, {json: true}, (err, resp, body) => { | |
if (err) return cb(err) | |
if (resp.statusCode < 200 || resp.statusCode > 299) { | |
return cb(new Error(`Status not 200, ${resp.statusCode}`)) | |
} | |
cb(null, body[propname]) | |
} | |
} |
There's not that much less boilerplate when you consider the extra work r2
is doing in comparison to request
. No reason you couldn't write an implementation of request
which sees something like { onlyJson: true }
and calls the callback with an error if status code is non-2xx -- there's nothing specific to async/await
that allows the api to do that.
const request = require('request')
let doJsonThing = (path, propname, cb) => {
request(`http://api.com${path}`, {onlyJson: true}, (err, body) => (err
? cb(err) : cb(null, body[propname])
)
}
Although using res.json
to signal that the user only wants the json body, in the form of a promise, is a nice touch.
Anyway, much of the remaining boilerplate is cut out by using promises instead of callbacks:
const request = require('request')
let doJsonThing = (path, propname) => {
return request(`http://api.com${path}`, {onlyJson: true})
.then(body => body[propname]);
}
(btw async/await is awesome and I love it, just let's call a spade a spade)
Agree with @bluepnume and want to point out that when using promises, getting property from returned JSON object is extra thing better left to functional library like Ramda. Also make a module that only gets JSON - since it is such a common case
const requestJson = require('request-json')
const R = require('ramda')
requestJson('http...').then(R.prop('user'))
So, I've been writing that 2xx checking boilerplate for the better part of 7 years in different projects. If there was an easy way to add it request
, I would have done so a long time ago.
Here's the problem, you have to signal to the API that you want JSON support (which adds accept headers). This also sets up the JSON decoding. However, a fair number of APIs return valid JSON bodies for their error conditions.
Because there's just a single handler (the callback) for socket errors, response errors, and success we can't add default status code checking for this case in request itself without blocking people from being able to handle their own http errors codes that have json bodies. It has been considered several times.
The reason you can do this w/ the promise API is that you have multiple entry points for the success condition. You can still support people checking their own 4xx errors by doing await r2(url).response
but when doing await r2(url).json
you can add some additional default semantics that make sense 99% of the time.
The real power here isn't anything as simple as callbacks vs. promises. The real power is in the fact that errors throw, and that you can customize what is considered an error based on what property is accessed. This allows you to add all kinds of semantics for different usages that you just can't inspect when all you have is a callback to handle every class of error and success.
You don't need users to plug and propagate errors by hand, that's all language level now. And because you aren't passing in this future handler for the error you can create multiple entry points for the success conditions.
@mikeal there's also response.ok
which maps to what you wrote. https://fetch.spec.whatwg.org/#ref-for-dom-response-ok①.
Wait I'm being an idiot, that was the old example. Ignore me.
Thanks for the detailed explanation, @mikeal -- interesting PoC
I want to believe you; I feel like there is something powerful I'm missing. But I don't understand your answer to @bluepnume and @bahmutov.
@jakearchibald: I'll take over the idiot badge ;)
Note: uses r2 library here https://github.com/mikeal/r2
This is a good example of how much unnecessary API you can shave off once you have language level constructs for async.
The real advantage is that several classes of errors can be created and handled implicitly by the caller like any other method call.