Sometimes we have some code:
fetch(" ... ")
.then(res => res.json())
.then(d => setData(d))
.catch(err => console.error(err));
Whenever we see the form
(value) => doSomething(value)
that is, get X
, and do something with X
, then we actually can change it to
doSomething
So the original code above, can be
fetch(" ... ")
.then(res => res.json())
.then(setData)
.catch(console.error);
and internally, we can convert .then(setData)
to .then(v => setData(v))
The exception is when doSomething
can take in more than one argument, but x => doSomething(x)
will be sure to only get the first argument.
This pattern works if it is the .then(setData)
case, where it takes a function that accepts one argument only.
Now, note that for the case of .then(console.log)
, it works, because log
as a function doesn't need to have the this
bound to console
. This is very subtle. When we provide console.log
to .then()
, we have the "lost binding" in JavaScript. So when .then()
invokes this function, the passed in function log
(or console.log
) does not have this
bound to console
. Note that if we invoke console.log(something)
, then when log
is running, the this
is bound to console
. In the case of console.log
, this is not important.
Note that we do .then(console.log)
, it is not any different from
const g = console.log;
fetch( ... )
.then(res => res.json())
.then(g)
It is easier to see the g
has the lost binding problem.
As a result, we need to be careful about
.then(a.b.c.fn)
because fn
has lost the binding to c
. To make the binding stay we have to use
.then(a.b.c.fn.bind(a.b.c))
And this brings another lesson in JavaScript: whenever we see something like foo.bar.fn
or foo.fn
, we need to be careful about the possibility of lost binding. And if we are debugging, being able to recognize this a.b.fn
pattern would help you identify the possible bug due to the famous JavaScript lost binding problem.
Top comments (10)
This is very error prone though!
As soon as the caller or callback can pass/take different arguments. And it will bite you as soon as you want to change the API (start passing more arguments to the callback, start taking more arguments in the callback)
Simple counter-example:
It's only error prone if the outer function calls the callback with arity greater than 1, though. It's perfectly safe with
.then
chains, because.then
only passes a single value, the value the promise resolves to.Fair point. You have to be careful when the number of arguments passed can be a problem. However the majority of the time, I use functions that only accept a single argument or on something like
.then
or.catch
which only passes a single argument, so I prefer OP's way for that.I can see the problem with inconsistency though, which might lead to the type of errors you mentioned. As they say, "consistency over perfection", which would lead me to choose your method if we had to pick just one.
But, in my opinion, the best solution is for everyone in the codebase to be comfortable with functional programming (which is probably not realistic). If that was the case, then the
parseInt
solution might be something like this:parseInt
is the classic example... in this case the extra index passed intoparseInt
... forsetData()
it should be ok IMHOIf you use
setData
withArray.prototype.map
, it makes it harder to add an (optional) second argument tosetData
without breaking.map(setData)
; whereas.map(d => setData(d))
would work just as well, always passing a single argument tosetData
.And using
.then(parseInt)
will make it harder for TC39 to possibly add a second argument to the ifFulfilled callback without breaking existing websites; whereas.then(d => parseInt(d))
would continue to work just as well, ignoring the second argument.Using the function directly as the callback only works if it's been designed for that exact usage only.
Given all that, one should use
d => setData(d)
as rule of thumb, making it explicit which arguments are expected and used.I guess that can make sense. When I first looked at some code that was like
I just thought it was kind of cool. I guess maybe just as something we are aware of.
Yes, completely agree. After getting used to functional programming the redundancy of
.then(d => something(d));
always sticks out to me as.then(something);
is equivalent.It's as though someone was writing:
Instead of just:
Sometimes I take it a step further and also shorten the
res.json()
line, although this one depends because sometimes it can be more confusing. For example:no
To be honest this works better as an argument against library authors making breaking changes, rather than an argument against library consumers using JavaScript to its full potential just in case library authors make breaking changes.
TypeScript.