I must be one of the luckiest developers on earth because I’ve been working with promises for…
Danielo Rodríguez
1

I don’t know what you mean with [“Promises are eager”].

I’m referring to the fact that a Promise is eagerly evaluated as opposed to lazily evaluated. Let’s look at an example:

const p = new Promise((resolve) => {
console.log('Hello world!');
resolve(true);
});

We create a Promise p but don’t yet bind any handlers (we haven’t called p.then()). Despite that fact, the Promise will happily go ahead and execute our function (yes - in the next tick, I’ll still refer to this as immediately). This is what I mean by eager. If we execute this piece of code, we see “Hello world!” despite never having bound any handlers.

By contrast, a lazily evaluated data structure remains “cold” until it’s told to run, similarly to a function. We’ll look at an example using Fluture:

const Future = require('fluture');
const f = Future((reject, resolve) => {
console.log('Hello world!');
resolve(true);
});

When we execute this code, nothing happens — the Future structure is built but never used. Only when we add f.fork(console.error, console.log) do we see our “Hello world” message. This means is that whoever gave us a Future didn’t only give us control over what happens with the result of its operation, but also over if and when the operation will happen. When I mentioned “control of side effects”, this is what I was referring to.

Surely cancellation may be a good feature, but I never found a situation where I want specifically a race condition

Every situation where you are interested in the first result of multiple Promises, this is a use-case common enough for Promise.race to be included in native Promises. A trivial example would be a request timeout:

const getBob = () => Promise.race([
get('/users/bob'),
new Promise((l, r) => setTimeout(r, 20000, 'request timeout'))
]);

With Promises, the timeout will always occupy the event loop for 20 seconds — even if the get request resolves after 1 ms. This means that if you run many concurrent requests, you’re using a lot of resources needlessly. If Promises would support cancellation, the timeout would be cleared as soon as the winner of the race is known.

handling exceptions vs expected errors, and that smells a bit Java to me

Woah woah! That was uncalled for.

If I want to have expected errors, instead of throwing an exception (which for me is something as bad as break on loops or goto) I just return a flag on my result

Aha! We are at a simple misunderstanding. It seems you have found your own way to separate expected failures from exceptions. I see this as reinforcement: Promises mix expected failures with exceptions, which forced you as a user to use another mechanism for handling expected failures. The Promise rejection branch would be the ideal place for your flagged result to reside, because it allows you to recover from it at a later stage, but because the rejection branch may also contain exceptions, you cannot use it anymore. Now you probably do something like:

getUser().then(user => {
if(user.isFlagged) return user; //<- custom control flow
return doThingsWithUser(user);
}).then(result => {
if(result.isFlagged) return handleFailure(result);
return handleSuccess(result)
}, err => handleException(err));

This custom piece of control flow would not be necessary if it weren’t for Promises catching exceptions. With Fluture:

getUser()
.chain(doThingsWithUser)
.fork(handleFailure, handleSuccess)
process.on('uncaughtException', handleException)
I never found myself creating an object with a then method

Imagine you have an array of User models, looking somewhat like this:

const User = ({handle, firstName, lastName}) => ({
handle,
firstName,
lastName,
getName: () => `${firstName} ${lastName}`
});
const getUsers = () => Promise.resolve([
User({
handle: 'avaq',
firstName: 'Aldwin',
lastName: 'Vlasblom'
}),
...
]);

Of course, the Array of Users is the result of a read from the database, or a get from the API, the point being it arrives inside a Promise.

Now we would like a function getNameByHandle to compute a full name by handle. In order to make it fast, we first render a StrMap of getters, using Lodash keyBy and mapValues :

getUsers()
.then(users => _.mapValues(_.keyBy(users, 'handle'), 'getName'))
.then(index => {
  const getNameByHandle = handle => index[handle]();
  //Here we would do more with the function we created.
//For now I'll leave it at an example.
return getNameByHandle('avaq');
})
.then(console.log, console.error);

Though contrived, this code is not so crazy and it works just fine, until some person registers with our service using then as a handle. At that point, on line 2 we will return an object with a then function. The Promise will think it’s a Promise and try to consume it, the program will never get to line 3.

I’m trying to illustrate that Promises have made it dangerous to pass mappings from string to function down the pipeline. If you think back to all the programs you’ve written with Promises, can you guarantee that you’ve never passed an object of functions where (one of) the keys were created by user input? Even if you haven’t, it’s something to take into account — one of these subtle things you have to be aware of when using Promise.

The same thing happens to other special methods like toString or toJSON

They suffer from the same problems, though the difference is that toString and toJSON are not at the founding layer of our programs.

most of programmers that I saw claiming about promises had small knowledge about them

This is exactly the problem. Many of the problems with Promises are simply not talked about, most articles praising Promises skip right over them. I’m hoping to bring these issues to light.

I’m not saying that you don’t have enough knowledge about promises

I’ve been using Promise for six years, right from since their first flawed implementations landed in jQuery. I used to contribute a little bit to Bluebird, and I’ve created my own fully spec-compliant Promises/A+ implementation. That said, I don’t really like arguing from a position of authority. I hope that my reasoning alone is enough to convince you of my arguments.