top | item 9564076

We have a problem with promises

164 points| nolanl | 11 years ago |pouchdb.com | reply

102 comments

order
[+] falcolas|11 years ago|reply
Callback heavy languages can be very challenging to think about. They require keeping quite a few disparate pieces in your mental stack all at once.

Promises make this both better and worse, in my experience. In the easy cases, all you have to do is follow the promises syntax, and you can treat it as linear code.

The problem arises when writing non happy path code, you not only have to think about the promises syntax, but about the abstraction it's creating over callbacks. Otherwise, you begin to miss corner cases, create incorrect promises, and in general write code which misbehaves (often silently).

Promises are powerful abstractions, but like all abstractions, they have their leaks. Also like all other abstractions, there are places where you have to fully comprehend the mechanisms underneath them to use them properly.

[+] Bahamut|11 years ago|reply
I like doing stuff like

  function ensureFoo() {
    let promise = Promise.resolve();
    
    if (!foo) {
      promise = getFoo();
    }
    
    return promise;
  }
[+] hippich|11 years ago|reply
In my experience, whenever you having trouble with callbacks/promises - it is time to refractor your code. Essentially, this JavaScript nature force you to write code with more code blocks, which is in the end very beneficial to testing.
[+] adrusi|11 years ago|reply
The real confusion here is dynamic typing. It's weird that the parameter to .then() can return any value, but if it's a promise there's special behavior. And for no good reason either

    p.then(r => r + 5);
Is just a bit of sugar for

    p.then(r => Promise.resolve(r + 5));
And then the type signatures are easy to reason about. The dynamic typing also risks introducing hard to find bugs. Suppose you have an heterogeneous array `things` which might contain numbers and promises which will resolve to numbers.

    p.then(r => things[r])
     .then(thing => selectedThings.push(thing));
You might intend `selectedThings` to also contain either numbers or promises that resolve to numbers, ordered deterministically as a function of `p`, but instead it will contain only numbers and its order is nondeterministic (it depends on `p` and all of the promise members of `things` that `p` ever signals).
[+] noelwelsh|11 years ago|reply
This is exactly right. If the spec defined an API with simple semantics half these problems wouldn't exist. As it is `then` is overloaded to perform numerous distinct operations. This means it can't report any useful errors, because any value passed to `then` is valid. There was even discussion about this when the spec was being written, but it was decided the current semantics are more in keeping with Javascript. Unfortunately the spec authors were correct.
[+] Guillaume86|11 years ago|reply
I don't agree with your first remark: what do you expect to be returned when you return a value not wrapped in a promise?

The ambiguity is actually the auto unwrapping of promises for subsequent then calls but I've yet to see a case where it's a problem (it's actually what makes the API clean looking when used).

For your last snippet, I don't see an array so I don't know what ordering you want to preserve (p always returns the same value so r will always be the same), if it's actually inside a loop, you should use Promise.all or Promise.map.

[+] vkjv|11 years ago|reply
> Rookie mistake #3: forgetting to add .catch()

I disagree with this advice. I usually refer to this as a "Pokémon Catch" (Gotta' Catch 'Em All!). Never catch a promise that you are un-able to handle. It's better to use a Promise library that supports debugging possibly unhandled exceptions (e.g., Bluebird).

> Rookie mistake #4: using "deferred"

This one is interesting because it's actually used in the Q library documentation for a way to `denodeify` or `promisify` a function. You definitely shouldn't do this if it's already a promise, but outside of that, it's more of a gray area. I tend to recommend using libraries to convert callback functions to promises.

[+] phs2501|11 years ago|reply
The problem with "var d = defer(); ...; return d.promise;" vs "return new Promise((resolve, reject) => {...});" is that the former does not protect you from synchronous throws. In the latter (at least with every promise library and native promise implementation I've seen) if the code in '...' throws, it rejects the promise with the thrown error. In the former, it throws synchronously, which especially in node is almost never what you want.

Besides, if you need to promisify a node-like function you should just use Bluebird's promisification features; no point in doing it yourself. The fact that Bluebird is also the most debuggable and performant Promise implementation (including native) is just icing on the cake.

[+] inglor|11 years ago|reply
Yes, adding `catch` is mostly an anti-pattern today, most promise libraries as well as native promises on most platforms will detect unhandled rejections for you if you know the hooks. Bluebird is particularly good at this, but Q, When, RSVP and native promises all do this.
[+] greggman|11 years ago|reply
I would argue that `Promise.all` is not the equivalent of `forEach` because Promise.all will execute everything immediately, maybe all will fail, maybe 1 will fail, eventually you'll have your then or your catch called but 100 of your actions will have an attempt to be executed.

Compare that to forEach, if one fails and you throw the rest don't get executed.

I suppose if whatever you're doing for each thing is async then they are equivalent equivalent but in general forEach has different sementics than Promise.all

[+] nolanl|11 years ago|reply
You're right; I'm playing a bit fast and loose with the definition of "equivalent." However, I think it's what most people want when they reach for the forEach().

In that "Promise protips" gist (https://gist.github.com/nolanlawson/6ce81186421d2fa109a4), I have an example of something closer to what you describe: the sequentialize() function will execute one promise after the other, and fail as soon as a single one fails.

However, if you need to actually run multiple promises in parallel, but cancel them as soon as a single one fails, then you might want something more advanced than promises, like RactiveJS (https://github.com/ractivejs/ractive) or RxJS (https://github.com/Reactive-Extensions/RxJS).

[+] MasterScrat|11 years ago|reply
Now I wonder what would be the good way to make a forEach that actually returns when all elements have been executed?
[+] STRML|11 years ago|reply
Great article; a few things I would add. I use bluebird for Promises, which is just the most fantastic Promises lib ever conceived, no joke; if you haven't used it try it. So some of these may be Bluebird-specific:

1. Don't wrap callback functions manually with `new Promise(function(resolve, reject) {...})`, just use `Promise.promisify(...)`. For one-off functions, try `Promise.fromNode(function(cb) { fs.readFile('..', cb); });`.

2. This pattern:

  getUserByName('nolan').then(function (user) {
    return getUserAccountById(user.id);
  }).then(function (userAccount) {
    // I got a user account!
  });

Could be:

  getUserByName('nolan')
  .get('id')
  .then(getUserAccountById) 
  .then(function (userAccount) {
    // I got a user account!
  });
3. I too used Promise.resolve().then(...) to start a lot of route handlers. Try `Promise.try(function() {...})`, which is equivalent but reduces the temptation to just stick a synchronous value in the `Promise.resolve()` just because you can.

4. `Promise#nodeify()` is super useful for creating functions that return promises or use callbacks depending on how they're called. For example:

  function getUserName(id, cb) {
    return db.getUserAsync(id).get('name')
    .nodeify(cb);
  }
Is the same as:

  function getUserName(id, cb) {
    var promise = db.getUserAsync(id).get('name');
    if (!cb) return promise;
    promise.then(function(result) { cb(null, result);})
    .catch(function(e) { cb(e); });
  }
This is great if you want to convert a few functions you use to promises, but they're called elsewhere and expect a callback style.

I'm sure there are many more. This is my bible: https://github.com/petkaantonov/bluebird/blob/master/API.md

In short; use Promises! They are the answer for callback hell in Node. Async/await may fix more problems in the future but if you want Node/Browser compatibility and to get started right now, Promises are the best way to go.

[+] loganfsmyth|11 years ago|reply
Overall, totally agree, just wanted to mention two things.

2) I've always found passing callbacks by name to be very difficult to read/follow, but this is a case where I think ES6 arrow functions will help readability a lot.

    getUserByName('nolan')
        .then(user => getUserAccountById(user.id))
        .then(userAccount => // I got a user account!)
4) Your example isn't quite equivalent, you should be using the 2-param version of .then(), not .then().catch(). In your current code, if your cb threw an exception, it would call it a second time with the error from the first time.
[+] nolanl|11 years ago|reply
Bluebird is awesome. :) I especially love promisifyAll() and promisify().

The only reason I didn't include something like that in the post is that I didn't want to overwhelm newbies. I think it's already confusing enough what the difference is between q/when/RSVP/bluebird/jQuery promises/Angular promises, etc... And honestly, more and more I just use vanilla promises, or a tiny polyfill like Lie.

[+] vkjv|11 years ago|reply
Also, fun is that `.nodeify(cb)` protects against undefined. So, it's a handy way to make your library support both promises and callbacks.

    module.exports = function (arg, optionalCb) {
        return promiseReturningFn(arg).nodeify(optionalCb);
    };
[+] davexunit|11 years ago|reply
The messes that callback/promise-heavy JavaScript make are a good example of why we need syntactic abstraction in the language. With a proper macro system, this whole async mess could be abstracted away and we could write nice linear code.
[+] mbrock|11 years ago|reply
In Scheme, for example, a solution would be likely to involve delimited reified continuations and not need much in the way of macros. It's not obvious to me that the problem can be solved accurately with just syntactic macros. Would the solution involve transforming code into CPS?
[+] Too|11 years ago|reply
Yeah, i cant believe async support is not a required check box for programming languages nowadays. Almost all the code I write is asynchronous and it is a pita to always wrestle around different patterns exposed by various libraries, and write bloat for things that are essentially sequential just that they happen to involve a lot of io latency. No, instead we get syntax sugar for writing shorter get/set-properties.
[+] johnnymonster|11 years ago|reply
its interesting that when people talk about the newest feature or framework in javascript, they tend to forget about javascript's core functionality. It's always necessary to look at things like promises with javascript's core functionality in mind. Take #3 for example, which is really just a function of the core way javascript executes, taking promises aside!

You would not do something like this would you in a normal day at the office? function myFunction(doSomething()){

};

so why would you do this.

Promise.then(doSomething());

doSomething() gets immediately invoked when the code is evaluated. It has nothing to do with Promises.

Don't forget the fundamentals!

[+] robmcm|11 years ago|reply
This article has a lot of ambiguity which I think adds to the confusion.

For example I was looking at that question(3) thinking, does doSomethingElse() return a function, another promise, nothing?

Part of the problem of having a very forgiving, lose language and API is that it leads to hidden, subtle complications, highlighted by "Advanced mistake #5: promises fall through". Here the then() method accepts anything as a return type, but will only chain a function that returns a promise, not a promise it's self. This isn't present in the MDN documentation (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...).

[+] juliangregorian|11 years ago|reply
Well, not necessarily. Let's say doSomething is a closure that itself returns a function. Then it makes perfect sense to call it that way.
[+] LordHumungous|11 years ago|reply
>If you know the answer, then congratulations: you're a promises ninja

No... you know how functions work in javascript.

[+] hinkley|11 years ago|reply
And yet, I had to give a presentation at my last contract with basically the same information in this blog post because half the team was getting it wrong and the other half was tired of cleaning up.

Not everybody knows their stuff (especially junior developers). In front end especially I've found people who can run CSS circles around me but can't, for instance, filter data returned from the server on more than one criteria without creating spaghetti in the process.

Most places you don't get to pick who you'll work with. Even when you sit in on the interview loop, shit happens. Railing against it (and I have, at length) has never done me a lick of good, and trying to maintain the fantasy that it would has gotten me nothing but trouble.

At some point you just have to identify what things people are good at and steer them away from working on things they suck at. And keep your resume up to date.

[+] simonw|11 years ago|reply
Understanding functions in JavaScript is not enough to be able to complete the quiz. You also need to understand the somewhat surprising semantics of the Promises .then() method.
[+] ttty|11 years ago|reply
Yes, I'm not anymore a heavy user of fibers and I could answer correctly all these questions. Is pretty much vanilla js with some little knowledge about promises.

Anyway I would suggest anyone to take a look into fibers, you can write sync non-blocking code which is a delight to work with. No need of callbacks and promises.

[+] xtrumanx|11 years ago|reply
What's so bad about using deferred? In one case I call the resolve or reject parameters and in the other I call a resolve or reject properties on the deferred object. Not much of a difference to me.

I learned about deferred a few years ago and kinda stuck with it. It's all over my code base and he didn't really justify why I should go about changing it. The only thing I can reason I can think of is using his recommendation follows the ES6 spec which doesn't matter to me that much for now.

[+] Lio|11 years ago|reply
The main issue I've found is with the way that deferreds handle then failures and how "then" works.

According to the Promises/A+ spec, then should always return a new promise. With the jQuery deferred.then() that's not the case.

In practice this means that it's harder to recover from failures as you're processing your results because your all your onRejected callbacks are attached to the same deferred and so will all fire one after another.

If you're just dealing with one asynchronous event, for example an AJAX call, that might not be a problem but it can cause problems debugging more complex code if you're not very careful.

E.g. (Get request) -then-> (onGetSuccessProcessor, onGetReject) -then-> (onProcessSuccess, onProcessingReject)

If the get request fails the onProcessingReject will also execute even though we haven't run the onGetSuccessProcessor callback.

In Promises/A+ code you can return a new resolved promise from your onRejected callback and the next "then" will fire it's onResolve callback instead of onRejected.

Hope that helps. (it's a lot easier to draw as a petri net than to explain in a comment on HN :)

[+] esailija|11 years ago|reply
It's just mountains of unnecessary extra code that is error prone because you have to manually wire it. When you chain a promise or promisify a callback instead of using a deferred or the promise constructor (both are just as evil here), the wiring is done implicitly and there is a lot less code.

Edit: What I mean by "wiring" is that you need to make sure that in all successful code paths `deferred.resolve` is called and that in all erroneous code paths (not always a single try catch either) `deferred.reject` is called.

[+] tel|11 years ago|reply
Re "Advanced Mistake #4", do Javascript programmers not know about tuples? Does nobody write functions with signatures like

    pair(pa: Promise<A>, pb: (a: A) => Promise<B>): Promise<{fst: A, snd: B}> {
      
      return pa.then(function (a) { 
        return pb(a).map(function (b) {
          return {fst: a, snd: b}
        })
      })

    }
It's a super reusable little chunk of code.
[+] phs2501|11 years ago|reply
It is, but it only works for pairs and then you wind up with the relatively nonsensical 'fst' and 'snd' names.

My preference would be:

  let user = await getUserByName('nolan');
  let userAccount = await getUserAccountById(user.id);
  // stuff with user and userAccount
But I have long since decided that I am never writing ES5 again, and since I therefore need Babel anyways I may as well turn 'stage: 1' on and use async/await. It's basically do-notation for promises.
[+] arxpoetica|11 years ago|reply
I made the transition from callbacks to generators/iterators recently, and I'm really enjoying the yield/next foo. Promises just never really spoke to me. Not certain why I always felt reticent to use them.
[+] embwbam|11 years ago|reply
Typescript or Flow will catch many of these errors. Highly recommended!
[+] tel|11 years ago|reply
Also having promise libraries which stuck closer to the basic monadic interface without lots of overloading would help things like Typescript and Flow catch all of the errors.
[+] giulianob|11 years ago|reply
Yeah, just sounds like a type issue that any simple static checking would catch.
[+] tel|11 years ago|reply
And this is why types are nice. Along with limited overloading.
[+] neumino|11 years ago|reply
Gosh, adding a `.catch(console.log.bind(console))` is just insane.

If you have a library that will return non operational error, just remove it from your project. If your code throws when it is not expected to, fix it.

This is like saying put a `try/catch(ignore)` everywhere. Seriously.

[+] ben336|11 years ago|reply
Logging errors to the console can be helpful to find them during debugging/development (as opposed to having them fail silently).

The goal isn't to leave errors in, its to expose them when something changes to make them happen so that you can do the 2 things above.

[+] nolanl|11 years ago|reply
Yep, Chrome is pretty great about printing unhandled promise rejections to the console. However, I just tested Firefox and Safari, and neither of them will warn you in that case - not to mention older browsers like Android <4.4 and IE <=11, which don't even have native promises!

So if you're ever trying to debug promises in a non-Chrome browser, you may find my "insane" advice pretty helpful. :)

[+] Too|11 years ago|reply
Yeah. In chrome if you enable "break on exceptions" it will also break on unhandled promise rejections. This will take care of all your debugging needs.
[+] mweibel|11 years ago|reply
Very nice article, will keep that in mind when trying to help others with promises. Also helped me to re-understand some things :)

One thing though, Advanced mistake #4, is in my opinion good answer, the Q library however gives a (afaik) non-a+-standard way of doing that which I like:

from:

  getUserByName('nolan').then(function (user) {
    return getUserAccountById(user.id);
  }).then(function (userAccount) {
    // dangit, I need the "user" object too!
  });
to:

  getUserByName('nolan').then(function (user) {
    return [user, getUserAccountById(user.id)];
  }).spread(function (user, userAccount) {
    // I do have the user object here
  });
[+] tumbling_stone|11 years ago|reply
The most comprehensive and foolproof way is to grab the spec, read the algorithm and fiddle around a day. Sadly this is the only way of fully understand promises, promises already put a lot of cognitive load on your brain when you're using them, so having any other abstractions of your own (for remembering how promises work) is bad idea. IMO you're better off investing a large continuous block of time for understanding promises rather than reading some article here and there.
[+] janfoeh|11 years ago|reply
I am stumbling over this:

> Just remember: any code that might throw synchronously is a good candidate for a nearly-impossible-to-debug swallowed error somewhere down the line.

Why would a synchronously thrown error be swallowed, and why would I not just `try { } catch` here?

[+] nolanl|11 years ago|reply

  naivePromiseAPI() {
    if (foo) {
      throw new Error('this will get swallowed!');
    }
    return somePromise();
  }
Instead you want this:

  cleverPromiseAPI() {
    return Promise.resolve().then(function () {
      if (foo) {
        throw new Error('this won't get swallowed!');
      }
      return somePromise();
    });
  }
Because if the client of the API does something like this:

  $('.my-button').on('click', function () {
    cleverPromiseAPI().catch(console.log.bind(console));
  });

Then the client might expect the error to get console.logged, but actually it won't in the naive case. That's because the error was thrown synchronously and thus bubbled up to the click handler, instead of being caught inside of the promise chain.