wewq
wewq

Reputation: 229

Confusion with how thenable callback works in Promise?

I am new to JS and was learning promises. The code excerpt I want to show is this

promisedFunction.then(data=>console.log(data))

or simply

promisedFunction.then(console.log)

which is equivalent of the former code excerpt. The question is how is that possible to just use then(console.log) instead of then(data=>console.log(data))? Does it mean that we can omit the passed-from-promise data in thenable callback?

Upvotes: 2

Views: 271

Answers (4)

VLAZ
VLAZ

Reputation: 29085

data=>console.log(data) is a function that takes a parameter and calls a method with the passed in argument.

If you pass console.log it will execute the same method and still pass the same argument.

In effect you are dropping the extra function call - slightly more abstractly, imagine this:

//some function `f`
const f = x => x + 1;

//different function `g` that just forwards the call to `f`:
const g = x => f(x);

console.log(g(41)); //42

The definition of g is just a function that all it does is call f. Which has exactly the same effect as the f function. So, we can just re-write it as:

//some function `f`
const f = x => x + 1;

//different function `g` that just forwards the call to `f`:
const g = f;

console.log(g(41)); //42

and get exactly the same effect.

In Lambda Calculus, this removal of an essentially empty "wrapper function" is called Eta reduction.

Therefore, yes, both .then(data => console.log(data)) and .then(console.log) do exactly the same, you're performing the same sort of conversion where you're taking out a dummy forwarding function.


However, that's not always an option. For example, if removing the forwarding function will end up calling the target with more parameters, you can get different behaviour. An infamous example is parseInt when used in .map:

const arrayOfString = ["1", "2", "3"];

const arrayOfIntegers1 = arrayOfString.map(parseInt);
const arrayOfIntegers2 = arrayOfString.map(x => parseInt(x));

console.log("arrayOfIntegers1", arrayOfIntegers1);
console.log("arrayOfIntegers2", arrayOfIntegers2);

The issue is that Array#map calls the callback with three parameters - the item, the index, and the array. And parseInt has an optional second parameter - if passed in, it changes how the number is parsed. So you get NaN.

The easiest way to observe this is with console.log:

const arrayOfString = ["1", "2", "3"];

console.log(" --- using just `console.log` as callback ---");
arrayOfString.map(console.log);

console.log(" --- using just `x => console.log(x)` as callback ---");
const arrayOfIntegers2 = arrayOfString.map(x => console.log(x));

So, dropping the intermediate function works as long as it has the same arity as what it will be called with and what the next function will be called with.

Upvotes: 3

Nick Parsons
Nick Parsons

Reputation: 50854

data => console.log(data) is a function which takes in data and then calls the function console.log with data as its argument. So, it's a function which gets given data and then it gives that exact same data argument to the console.log function.

As you know console.log is a function, so, you can place console.log instead of your arrow function. This way, it will be called with the data argument and any other arguments natively passed into .then()'s onFulfilled callback (in this case it only gets given the resolved value of the promise).

Take the following example below. bar gets given "hello", which it gives to foo, foo accepts "hello" and returns "hello" back to where it was called, so, bar ends up returning "hello":

const foo = x => x;
const bar = x => foo(x); 

console.log(bar("hello"));

In the above example, bar acts somewhat like a middle-man, as all it does is pass x to foo. This middle-step can be removed, by straight-up assigning bar to foo. This way, when we call bar(), we are executing the code defined within the function foo. This is the same idea that allows you to remove the need to explicitly defined your arrow function in .then():

const foo = x => x;
const bar = foo; 

console.log(bar("hello"));

This style of coding is known as point-free style code and, in this case, is achieved by eta-reduction.

Upvotes: 1

jeprubio
jeprubio

Reputation: 18012

promisedFunction.then expects a function to which will receive the data as a parameter.

in the case of data=>console.log(data) is an array function equal to:

function(data) {
   console.log(data);
}

and console.log is also a function which prints in console what receives as a parameter.

That's why console.log works.

If you had:

var print = function(data) {
   console.log(data);
}

promisedFunction.then(print) would also work.

See this working example that will resolve after 2 secs:

const doSomething = function(){
  return new Promise((resolve, reject) => {
    let wait = setTimeout(() => {
    resolve('Promise resolved!');
  }, 2000)
  })
};

var print = function(data) {
   console.log(data);
}

doSomething().then(print)

Upvotes: 0

user6827691
user6827691

Reputation:

The return value of promise or the "resolved value" is the input to callback passed in then. This behavior is not specific to promise but even [1,2,3].forEach(console.log) behaves the same where the 3 arguments of forEach are passed to console log. Thus you get ,

1 0 Array(3) [ 1, 2, 3 ]

2 1 Array(3) [ 1, 2, 3 ]

3 2 Array(3) [ 1, 2, 3 ]

Upvotes: 0

Related Questions