With regard to these great two sources: NZakas - Returning Promises in Promise Chains and MDN Promises, I would like to ask the following:
Each time that we return a
I'll try to answer the question "why then callbacks can return Promises themselves" more canonical. To take a different angle, I compare Promises with a less complex and confusing container type - Arrays.
A Promise is a container for a future value.
An Array is a container for an arbitrary number of values.
We can't apply normal functions to container types:
const sqr = x => x * x;
const xs = [1,2,3];
const p = Promise.resolve(3);
sqr(xs); // fails
sqr(p); // fails
We need a mechanism to lift them into the context of a specific container:
xs.map(sqr); // [1,4,9]
p.then(sqr); // Promise {[[PromiseValue]]: 9}
But what happens when the provided function itself returns a container of the same type?
const sqra = x => [x * x];
const sqrp = x => Promise.resolve(x * x);
const xs = [1,2,3];
const p = Promise.resolve(3);
xs.map(sqra); // [[1],[4],[9]]
p.then(sqrp); // Promise {[[PromiseValue]]: 9}
sqra acts like expected. It just returns a nested container with the correct values. This is obviously not very useful though.
But how can the result of sqrp be interpreted? If we follow our own logic, it had to be something like Promise {[[PromiseValue]]: Promise {[[PromiseValue]]: 9}} - but it is not. So what magic is going on here?
To reconstruct the mechanism we merely need to adapt our map method a bit:
const flatten = f => x => f(x)[0];
const sqra = x => [x * x];
const sqrp = x => Promise.resolve(x * x);
const xs = [1,2,3];
xs.map(flatten(sqra))
flatten just takes a function and a value, applies the function to the value and unwraps the result, thus it reduces a nested array structure by one level.
Simply put, then in the context of Promises is equivalent to map combined with flatten in the context of Arrays. This behavior is extremely important. We can apply not only normal functions to a Promise but also functions that itself return a Promise.
In fact this is the domain of functional programming. A Promise is a specific implementation of a monad, then is bind/chain and a function that returns a Promise is a monadic function. When you understand the Promise API you basically understand all monads.