Hacker News new | comments | show | ask | jobs | submit login
We have a problem with promises (pouchdb.com)
164 points by nolanl 980 days ago | hide | past | web | favorite | 102 comments



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.


Very true. And as I mention in the article, ES7 async/await should help fix that: http://pouchdb.com/2015/03/05/taming-the-async-beast-with-es...


Well, `async/await` introduces its own set of problems - I've answered questions on SO before where the OP didn't understand why they got degraded performance when they were doing `await` in a for loop instead of doing things concurrently. At the end of the day you still have to understand promises in order to use `async/await`


That's a fair point, but I think the name itself ("await") tends to communicate the the operation might take a long time. Compare that to multithreaded languages like Java, where it's not obvious that FileInputStream/HttpClient/etc. could take a long time!

Anyway, the big benefit of async/await, to me, is that you really DON'T have to understand promises. :) For instance, at some point we will probably add ES7 examples to the PouchDB API docs (e.g. here: http://pouchdb.com/api.html#create_document), and you can imagine that, from the user's perspective, the word "await" will just be part of the API (e.g. `var doc = await db.get('id')`), and beyond that, they won't have to learn anything about promises or async or callbacks or any of that.


What happens when they have to perform two things that are unrelated and suddenly their performance is down the drain because they `await`ed them all?


I like doing stuff like

  function ensureFoo() {
    let promise = Promise.resolve();
    
    if (!foo) {
      promise = getFoo();
    }
    
    return promise;
  }


Shoudn't you store the initial fooPromise and use it instead?

    var ensureFooEquivalent = getFoo();


Sometimes the data you want is already available - no need to make an extra request in those cases.


Didn't you mean to `Promise.resolve(foo)`? It's also generally safer to cache promises and not values to avoid race conditions (multiple requests).


Check lodash's memoize method


Or don't, as it requires adding an extra library.


Lodash provides separate packages: https://www.npmjs.com/package/lodash.memoize


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.


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).


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.


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.


I think the confusion stems from the fact that .then() acts like either .map() or .flatMap() depending on the return type.

If .then() was just .flatMap(), you could expect an error if you didn't return a Promise (for example, reject with a TypeError).

If .then() was just .map(), you could return a value of any type (for example, string or Promise<number>) and get back a Promise for a value of that type (Promise<string> or Promise<Promise<number>>).


Actually, it's slightly more complex because if the result of the function passed to then were somehow a Promise<Promise<something>>, it would be flattened recursively so that the ultimate result is just Promise<something>. (Of course, due to .then's flattening semantics it's highly unlikely you'd have a Promise<Promise<...>> in the first places).

The distinction between map and flatMap makes sense for, say, lists, because you often do want to keep the non-flattened structure around. From promises, I can't think of a reason why that would be desired. You just want to represent the final result after all necessary waiting is completed. I suppose a library could provide stricter map and flatMap for people who deeply care about this.


what do you expect to be returned when you return a value not wrapped in a promise?

Nothing that's a type error, it should throw a runtime exception just like all type errors in javascript, and it will show up in the debugger for the programmer to fix.

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).

I'm not sure I quite understand what you're referring to, but I think you mean the fact that you can chain then calls

    Promise.resolve(5)
      .then(x => Promise.resolve(x + 5))
      .then(x => Promise.resolve(x + 5))
      .then(x => {
        // x == 15
      });
This is totally sound behavior. The simplified signature of `.then()` is

    then :: Promise a -> (a -> Promise b) -> Promise b
This makes `.then()` the `bind` function for the Promise monad. As a refresher for people who don't think about monads much:

    (>>=) :: Monad m => m a -> (a -> m b) -> m b
While on this tangent, I'll note that `Promise.resolve()` is the `return` function for the promise monad:

    return :: Monad m => a -> m a
    Promise.resolve :: a -> Promise a
For your last snippet, I don't see an array so I don't know what ordering you want to preserve

It's a contrived example demonstrating how switching based on whether the return value is a promise can make hard to detect bugs. It's hard to come up with a short example for this because strange values turning up where they don't belong is a property of large, dynamically typed systems. So yes, the example doesn't use all the best practices.


Well I can't really see any benefit to force the wrapping all returned values in promises without a proper example.

The then() signature can be expressed in typescript and I suppose in alternatives like flow (using the union type): https://github.com/borisyankov/DefinitelyTyped/blob/master/e... .


Sure, you can represent it that way, but it needlessly complicates the semantics of `then`. If they were merely monadic bind then it'd easily be 1/20th as confusing.

The "overload everything every which way and assume that people are really sensitive to the differences" strategy just begs misunderstanding.

Monadic bind as the core and basis of your API is fantastic. It is exactly enough and no more than you need to define effectful sequencing semantics and, if you like, you can build everything atop it. And, honestly, you do anyway. But right now the design obscures the simple basis technology in order to have heaps of overload magic.


He's stating the problem is dynamic typing... so the expectation would be a type error (!).


" (..) but if it's a promise there's special behavior. And for no good reason either."

Just because you don't know the reason doesn't mean there isn't one. This is especially the case with a community-driven spec like PromiseA+.


Yup, this is another example of people not really understanding promises in JS too well.

Having to do `Promise.resolve(r + 5)` doesn't really solve that much in terms of typing, namely the fact you can return a rejected promise or throw an error and so on.

As someone who wrote a promise library or two in typed languages - I don't see it as solving it at all, although what's usually done is that `Promise.resolve` is aliased to `ok` or something similar to simplify the boilerplate.


Promises are monads. `ok` is `return :: a -> Promise a` and `then` is `bind :: Promise a -> (a -> Promise b) -> Promise b`. All other behavior arises from that, the monad laws, and the operations

    throw :: Error -> Promise a
    reject :: Promise a      -- I guess, not sure of the semantics here


> 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.


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.


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.


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


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).


It would also really help if you were verbose in the naming of your methods as to weather they were async or not. At first I didn't realise db.remove() was async (sorry perhaps that's my fault).

Also on your timing diagrams it may be worth indicating that methods don't start executing at the exact same time due to the single thread:

doSomething().then(function () { doSomethingElse(); }).then(finalHandler);

doSomething

|-----------------|

                  doSomethingElse(undefined)
..................|------------------|

                  finalHandler(undefined)
....................|------------------|

It's my understanding that the finally handler can not execute before the body of doSomethingElse has run. (Although I am unsure of a pretty/clear way to add this to a diagram ;) )


Now I wonder what would be the good way to make a forEach that actually returns when all elements have been executed?


Bluebird has this built in. For a quick userscript where I needed something similar, I created this which works ( warning: no unit tests, but it works for me )

https://gist.github.com/dcherman/4dfba0d72c008ee5b59d

It's a reduce function, but it could work as a forEach as well.


Bluebird has a `Promise.each` which does this. Also, you can use `thenable chaining` - make an initial promise and then chain it on each iteration - it becomes very similar to a for loop. I can add an example if you'd like.


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.


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.


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.


(disclaimer: I am the author of bluebird)

Thank you.

I must say the most prominent feature of bluebird has always been "debuggability" (not perf). Long stack traces, unhandled rejection reporting and warnings for many of the mistakes you cover (coming in 3.0, some of them listed at http://imgur.com/a/t3xng) are just priceless, especially when working with people who are not promise ninjas.


Those new warnings are FANTASTIC.

That's exactly what I meant when I said it's better for your tools to warn you, rather than trying to read and understand some lengthy article. :)

BTW for PouchDB, we use bluebird in Node and lie in the browser, and bluebird has been truly indispensable for debugging. Looking forward to it getting even better!


If you have a suggestion for warnings please open an issue https://github.com/petkaantonov/bluebird/issues

If you want to contribute we'd love docs contributions as you clearly have good technical writing. Here are the 3.0 docs (currently under work) https://github.com/petkaantonov/bluebird/tree/3.0docs/docs/d... any help with pages that explain promises conceptually (anti patterns page etc) would be highly appreciated.


I've got my hands pretty full with Pouch, but I'll take a look! :) Thanks.


After native promises, is there much reason to use 3rd party libraries? Promises are a pretty small set of tools, so I'm not sure what one would have to offer over another.


Bluebird is much faster and easier to debug than native promises. It is very feature rich and it runs _everywhere_. It also converts callback APIs to promises at one go.

That said, on a client-side app there are cases where I'd use native promises. On the server - not a chance.


Features, I suppose. Bluebird has many more helpers than the spec, and it may actually be faster than the first round of native Promises. We'll see how it evolves.


The stacktrace is much much better in Bluebird compared to native and there are lots of methods which are not available in the native one.


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);
    };


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.


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?


Yes, delimited continuations would be a big part of the solution in Scheme. The macros would just be the sugar on top of the whole system such that you didn't have to write out all of the lambdas and prompting and aborting of the continuations. I've implemented a coroutine system this way and it works nicely.


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.


This is why I think macro systems ought to be a must-have feature in modern programming languages. There's no way that language designers can anticipate every type of convenient syntax, nor would they accept everything that we might want. Then, if they decide to add the new syntax we've been clamoring for, we have to wait for the implementation that includes it to become widely available, especially so in the case of JavaScript. We lowly programmers should be able to put on our language designer hat and extend the syntax of our languages as needed.


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!


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...).


This. That was my "answer" to the question. "Oh, if `doSomethingElse()` returns a function, it will run that function after the promise resolves successfully."


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.


>If you know the answer, then congratulations: you're a promises ninja

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


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.


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.


You don't.

Just to drive this home: I've read the spec a few times when evaluating whether to integrate the concept into code I've written, soon after the pattern was introduced, and then again a year or so later. (Side note: the result of both evaluations was a "no thanks".) But at this point, I've never used or authored a Promise, ever. And before moving past the intro that contains the quiz in question, it's been over a year before I last looked at them, much less read any up-to-date spec.

The quiz in the intro doesn't actually do a good job of exercising the details of `then`, because they're all trivial; there's only a single use of `then` in each example, rather than a chain of multiple ones (or a chain involving `then` and `catch` for a less useful example). You can get past the quiz in the intro by having only a general understanding of how `then` works. In fact (and I can only speak for myself here, but this is how it played out when I went through it), not having an extensive familiarity with Promises may help for the quiz, because that knowledge isn't crowding your thinking, and all you see is function calls. The author points out the third example from the quiz as particularly sticky, but it was the easiest gotcha to catch with my mostly Promise-free background, because the effect where overlooking parts that can lead you astray (because you expect idiomatic patterns in that place) is pretty totally nonexistent.

It's the examples further into the article that you need to really look into and consider how `then` works. For example, in rookie mistakes #2 and #5, understanding how `undefined` is handled required backtracking to the intro where the spec is linked, in order to delve into interaction with "synchronous" return values, and `undefined` in particular.

Lesson for potential interviewers: don't rely on the intro quiz in this article for your fitness function to weed out candidates, because it could mean you're optimizing for the people you don't want (like me), rather than those you do. Do make sure to focus on the examples further in that actually exercise an understanding beyond the superficial.


I was able to answer it correctly with no knowledge of JS promises at all, although I have used them in other languages. Being told that there's something tricky going on was enough for me to be able to guess the difference between #1 and #2, and all I needed to know for #3 vs. #4 was that promises aren't a language extension that can capture expressions as thunks.


Thanks for corroborating!


The only part that confused me was that I forgot that Javascript doesn't do implicit-return-on-last-statement, hence was somewhat confused why #2 was different to #1 until I read the answer and facepalmed.


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.


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.


The main thing in my mind is that it means your code could have cases where it throws synchronously, and cases where the promise rejects with an error.

One place this is mentioned is: https://github.com/petkaantonov/bluebird/wiki/Promise-anti-p...


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 :)


Be careful with that assertion. jQuery's `.then` method has returned new Promises since 1.8.

In jQuery 3.0, their Promises will be fully Promises/A+ compliant as long as you're using `.then` ( `.done` and `.fail` are remaining non-compliant to remain non-breaking with sync XHR I believe )


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.


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.


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.


So alpha convert your names to whatever you like. I don't know how to do it namelessly in Javascript without Chuch encoding the pair.

You can go much further than this. The following operation

    ap<A, B>(pf: Promise<(a: A) => B>): (pa: Promise<A>) => Promise<B> {
      return (pa: Promise<A>) =>
        pf.then((f) => pa.then((a) => Promise.resolve(f(a))))
    }
can be used to extend the previous idea as far as you like.


I'm having trouble grokking whatever type-added syntax you're using for Javascript here. I'm guessing it's:

  function ap(pf) {
    return (pa) =>
      pf.then((f) => pa.then((a) => Promise.resolve(f(a))))
  }
Still, if I wanted to go full-hog static-typed functional I doubt I'd be using straight Javascript, I'd probably try one of the various Haskell-alike to JS converters. Promises with async/await syntax seems like the most practical of the various solutions whilst still feeling like Javascript.

(Besides, I prefer dynamic typing and I find points-free style in Haskell to be obfuscating rather than clarifying, so it seems unlikely we're going to have the same opinion of the best way to accomplish things here. :)


Sorry, I'm just writing in Typescript. It's the typed Javascript flavor I'm most familiar with. But, yep, you properly erased the types.

I'd love to use Haskell-to-JS but I don't think I could bet a product on it quite yet.


If anyone's confused by the 4 "puzzles," I whipped up a JSBin to demonstrate: http://jsbin.com/tuqukakawo/1/edit?js,console,output


What if the doSomethingElse in #3 returns a function that is equivalent to the doSomethingElse of #1? Shouldn't #3 then be identical to #1?


Yes, but in most code I've seen, that's a typo rather than someone intentionally writing a function that returns a function. You can definitely do it!


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.


Yield works with promises too.


Typescript or Flow will catch many of these errors. Highly recommended!


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.


Yeah, just sounds like a type issue that any simple static checking would catch.


And this is why types are nice. Along with limited overloading.


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.


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.


You need to configure or change your promise implementation if uncaught errors fail silently.

In any case, logging error gives you no information anyway. Just try running: `console.log(new Error("..."))`. So at the very least you should do `catch(function(e){console.log(e.stack);})`


You can just crash (and still see the stacktrace) and automatically restart. You shouldn't leave these `catch/ignore` in your code in prod, this is a pretty good way to get undefined behaviors and maybe corrupted data.


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. :)


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.


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
  });


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.


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?


  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.


Ah, I get it now, thanks! I missed the part about doing that for synchronous code in promise-returning APIs.

I had a moment of stupidity and first somehow understood that to be a general suggestion for error handling.


Puzzle number 3 doesn't have a complete explanation. DoSomethingElse() can return a function, which is then evaluated with the result of the first promise as an argument.


As soon as you can reasonably introduce an ES6/7 transpiler into your toolchain you should start using ES7's async/await or equivalently ES6's generators + a coroutine library function like Bluebird.coroutine, Q.async, co, or Task.js.

It solves basically all of the problems mentioned in this article.


hahahahaha this one cracked me up "Writing code without a stack is a lot like driving a car without a brake pedal: you don't realize how badly you need it, until you reach for it and it's not there."


I didn't really understand that one. First of all, I don't see why a stack really is that indispensable, and secondly, JavaScript does actually have a stack so I don't know how he thinks it's operating stackless - where do the local variables go?


In the classic, but widely misunderstood, "Goto Considered Harmful", Dijkstra's point is basically that structure programming is good because it produces a stack and an instruction pointer, which contains an immense quantity of useful information when a crash occurs. Goto is bad in this paper because when it is used as the only control flow construct, in a way difficult for a modern programmer to understand in a world where structured programming won so thoroughly that every language is structured, it produces no stack trace. You get a "Crash On Line 28430", with no clue how you got there.

Naive event-based code is somewhat like a goto, in that every time an event comes in, you get a new stack constructed for that event. While you then get a stack if you call functions from the initial handler, every event that comes in whacks the stack afresh. So it isn't "the same as" a goto, but it does hurt the stack. Instead of a stack frame that represents the whole of the program, you get a series of "island" stack frames.

In some sense, this is the most fundamental reason why event-based programming is so painful, and it's the problem that all these multitudinous programming "async" techniques are trying to get around. Promises, for instance, can be looked at as a partial way of having a sort of stack around a particular sequence of operations that may be interrupted in arbitrary locations by this event-induced stack discarding. In this sense they can also be viewed as an inner-platform problem; where the language has "try/catch", for loops, etc, promises end up trying to recreate all that in a second layer above the bottom layer, with all the corresponding complexity of embedding a language in the language.

If you then end up asking, "Well, what if we just didn't discard the stack in the first place?", you end up in Erlang/Go/Haskell/Scala land.


This is what I mean:

  doSomething(function (err) {
    doSomethingElse(function (err) {
      doYetAnotherThing(function (err) {
        throw new Error(
          "I don't bubble up to the top of the stack, " + 
          "because there is no stack!");
      });
    });
  });
BTW a great trick is to tick the little "Async" box in the Chrome Dev Tools, so you can get a full stacktrace showing exactly where you came from. It works for both callbacks and promises: http://www.html5rocks.com/en/tutorials/developertools/async-...


It's extra funny because without stiching together stack traces you lose the stack by using A+ Promises.


If devs don't want to read specs and instead read blog posts then its obvious why there is a problem.


Your snark would be appropriate if humans were logically omniscient, but we're not. Reading and comprehending a spec does not imply grokking all of its consequences.




Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | DMCA | Apply to YC | Contact

Search: