Learn JavaScript promises in about 70 minutes

From the continuing archives of "things which people at work were unclear on, so I put some education together to help them out", here is my guide to how promises work in JavaScript.

A few things I plan to add to this essay if I get the chance: syntax highlighting for those JavaScript snippets; maybe a button you can click to try the code out; optional example snippets for each of those numerous bullet points.

Putting all of this together into an explanation where it makes sense was much harder for me than just trying to understand promises in and of themselves, which was already pretty hard. This essay was intended to be brief, and to be read slowly. If you're coming in to this not understanding promises, I would love to hear whether you found this edifying and if not what you would like to have cleared up. Also let me know how long it took you to get to the end.

One of these days I will move those extra "files" here into the main qntm.org content management system. Before I can do that I need a better colour scheme and formatting, though.

Back to Code
Back to Things Of Interest

Discussion (16)

2016-09-10 02:27:22 by hobbs:

As someone who mostly does understand promises, I'd say that this is pretty good! I don't see any big lies or mistakes in it, but it avoids going into unnecessary and confusing depth.

One big insight that helped me understand promises is that callbacks are a form of enforced inversion-of-control. Instead of writing functions that return a value, you have to write a function that *takes a callback* that accepts a value. That's what makes callback-based code unnatural. Promises, then, are un-inversion of control (or really, double-inversion of control). It's still callbacks under the hood, but you can go back to writing code that returns things.

2016-09-10 06:49:04 by jbox:

As someone who doesn't understand javascript but has been reading about monads for the past couple days, I have to say that promises sounds exactly like an application of monads. Which is pretty nice because now I know of a real use of monads outside of a purely functional language.

2016-09-11 09:15:02 by Veky:

No, sorry. That was completely impenetrable for me. But I don't know JavaScript very well.

2016-09-12 17:08:58 by qntm:

Sorry, I should have made it clearer up front that this essay was intended for people who are already familiar with JavaScript in general.

2016-09-13 17:11:59 by Jymbob:

... Then discover you still need to support IE11 and spend another 2 hours researching polyfills...

2016-09-15 15:02:00 by Baughn:

IE11 isn't so bad. I've written code that still needs to support IE6. But let's leave that aside.

Building on the callback functionality that JS already gives us, another alternative is to use a transpiler that implements async/await, e.g. as seen in C#. It isn't as easy to retrofit, thus the transpiler, but in my experience it's a lot easier to explain.

The transpiler needs to implement a continuation-passing transform. More relevantly, if you don't have a transpiler and you don't have promises, it's possible to use continuation-passing style manually. It's not as convenient as promises, but it's better than ad-hoc use of callbacks.

2016-09-16 09:54:39 by m1el:

I think that Promises is the best thing that happened to the JS world, and this video just clicked in my head "a-ha, this is the mathematical meaning of Promises!"

https://vimeo.com/113707214

So another nice way to explain promises is to draw railways :)

As a side-note, <em>implementing</em> Promise is an interesting excercise.

2016-09-16 10:06:52 by m1el:

Another thing: JS is *very* dynamic, so "impossible for the value of x to change between these two lines of code" is sort of false :)

var scope = {};
scope.__defineGetter__('x', ((a=0)=>_=>a++)());
with (scope) {
// two lines of code ^^
  console.log(x);
  console.log(x);
// end
}

2016-09-16 10:37:57 by qntm:

Well, `x` wouldn't really be a "global variable" in that case, would it?

You could achieve the same result without using the strongly deprecated `with` statement or violating strict mode by doing this:

    var x = 7;

    var console = {
        log: function(b) {
            global.console.log(b);
            x++;
        }
    };

    console.log(x); // 7
    console.log(x); // 8

But at that point it's pretty obvious that you're already a JavaScript expert...

2016-09-18 15:47:05 by Tim McCormack:

Looks pretty good. I think it took me about 30 minutes, but I have a fair amount of experience with JS, callbacks, and RxJava. Hard to say for sure because I kept getting interrupted by a baby.

----

This part confused me a bit:

"We don't actually have to call then(); the executor callback will still be called and whatever task was listed there will still be carried out, or at least attempted."

Is that saying that the Promise will still execute, even if we don't register callbacks for it? If so, I'm confused about the definition of "executor callback" because in that case there shouldn't be one...

----

I'm uneasy with the idea that returning a Promise from an executor callback directly sets the state of the former Promise. What happens if you have a generic asynchronous algorithm that shuttles all sorts of data around, and some caller sends in a Promise instead of an integer? I don't like in-band signalling...

2016-09-19 22:44:47 by qntm:

> Is that saying that the Promise will still execute, even if we don't register callbacks for it? If so, I'm confused about the definition of "executor callback" because in that case there shouldn't be one...

The executor callback is the `function(resolve, reject) { ... }` which you can pass into the `Promise` constructor. This is not to be confused with the success callback and error callback, which you can register with any promise using `.then(function(value) { ... }, function(err) { ... })`.

> I'm uneasy with the idea that returning a Promise from an executor callback directly sets the state of the former Promise. What happens if you have a generic asynchronous algorithm that shuttles all sorts of data around, and some caller sends in a Promise instead of an integer? I don't like in-band signalling...

In that case you have an instance of that monolithic problem which afflicts all dynamically typed programming languages: any data can be passed into any function, so you have to either trust that the data is of the correct type, or manually check it and throw an exception if it isn't.

Yes, this makes it tricky to e.g. have a promise which "sorts" a list of promises and returns the "greatest" one as its successful result. I guess you'd have to box the input promises up somehow and unbox on return. But, hopefully, that's an extremely rare edge case.

2016-09-20 03:03:11 by Alexandre:

This tutorial was well timed for me and extremely useful. Thanks for writing it!

I don't know if this was your innovation or a convention that I've never noticed but highlighting the new code in red was super helpful.

Tiniest nitpick, repeated use of even:
"... this pattern can even find use even in functions ..."

2016-09-20 03:04:36 by Alexandre:

Oh also this took me about 60 minutes to get through. The 70 minute estimate was pretty close!

2016-09-23 23:04:55 by Dom Storey:

As always in depth and detailed. Promises make JS functional languages slightly more bearable!! :-)

2016-10-11 18:16:10 by GeorgeWL:

@Sam I'd love to hear more about the CMS you use for this some day btw.

Is it custom-built or is it a plugged in thing?

2016-10-11 18:17:59 by qntm:

I wrote it myself in PHP about eight years ago. The code is pretty ghastly.