A Brief history of promises
I Promise.
Promises are older than you think - they originate from
an idea born during the start of the space
race. I want to tell you a story of
how a concept from the 1960s made its way into your JS runtime.
Before we begin, it is important that we establish the difference between sync
actions and async actions. Now, we could talk about code and implementation
details, but I find an analogy to be better suited for this task.
If you frame the world and everything that we do as us interacting with an
asynchronous entity the distinction becomes easier to reason about. Picture
yourself driving a car down the road: you are listening to the radio, staying in
your lane, and carrying on a conversation with the passenger next to you. Now you
feel the urge to sneeze, and as we all know sneezing is the last thing you want
to be doing when you are driving. Try as you might you cannot stop yourself from
sneezing, and there you go - you sneeze and close your eyes. Everything else you
are doing at that moment does not "Stop" - it continues on - but you are
stuck in the sneezing state: frozen, eyes shut, driving at 65 miles per hour.
Sneezing is what I would call a synchronous real world action: when you are
sneezing nothing else matters because THAT IS ALL YOU ARE DOING.
Let me illustrate the point in code. Here you can see we print "hello", sleep for a while and then print
"bye". While we are asleep nothing else is going on in the program, it is just
sitting there, waiting - doing nothing else. When the process completes we continue on with our
program. The Important thing to take away here is the fact that while we are
asleep, NOTHING is happening in our program, Nothing can happen at all given this
paradigm. This is an important concept to keep in mind as we wander onto this
winding and murky async history path.
Opening our browser console today and typing Promise, we are greeted by a
Promise constructor. We can resolve this promise with our friend the horse, and
we can reject a promise with some fire.
And no this is not just nightly Chrome, this is a very very well supported API,
that you can use today without polyfills and without vendor scripts in both the
browser and in Node.
So you are thinking of course Sam, tell me something I do not already know jesh.
But I want to point out that being able to use this API is not the novel thing,
the novel thing is asking yourself Why can I do this today?
How did these 7 letters ("Promise") get joined together so precariously and yield such a useful
and powerful thing?
Ryan Dahl first presented Node.js in 2009, which as you know was one of the biggest shifts in JavaScript
development since Brendan Eich wrote the initial implementation of JavaScript. In his presentation at
JSConf, this early version version of Node had Promises… well at least what they called
"Promises"... Of course I wanted to find out exactly what this implementation looked like so
I cloned the Node.js repo to take a look at the logs (FAIR WARNING - this repo is kinda big and it took
30 minutes :( so I do not really recommend that you do it)
Looking at the repo we can see that in June 2009 the initial commit was landed
for Promises… let's go one step further and see how it was implemented.
Oh, here it is. Wait though… this is not a promise implementation, this is just an event emitter
wrapped up in a promise-like interface. But what was the intent and where did
this idea come from?
What is the lineage of this idea of a promise?
It was pretty tricky, however through the extensive use of wayback machine, emails, and personal interviews I was
able to piece together a reasonable history all the way from the early origins of Promises to the ES2015 spec.
To set the scene, this tale of the promises starts in 1961. In 1961, NASA was
busy with the Mercury space program, racing into space to catch up to Russia
after their somewhat surprise launch of Sputnik 1.
Ham
the
chimp had just returned from a successful trip into
space, and was on the cover of LIFE.
Ok… enough with Ham the chimp. Back to Promises we go…
In 61, along came a paper written about Algol - short for algorithmic language -
and was published in ACM. This paper described a method of compiling procedural statements.
I won't bore you with the details because I know everyone in the room here is super
familiar with algol already, but the important concept discussed in this paper
comes down to this idea of a "Thunk"A thunk is a compile time optimization which provides an address. When this thunk
is executed, some value will eventually be available at some standard location
(or at the given address). Now why is this important and what does this mean? This
"thunk" thing is the concept that a value reference will eventually be stored in
a given location at some time in the future. This should sound somewhat familiar
to the basic idea of a promise -- a value that will eventually represent some value in the future.
If we hop into the Delorean and fast forward to 1977, the first Star Wars had
just been released and an equally important concept was introduced. A
paper
outlining the concept of a "future" was published.
While this paper was about approaches for dealing with garbage collection, it
has a few novel ideas that drove inspiration for future iterations on the
concept of eventual values, and remote execution of functions.
In the paper abstract right away the author throws out this concept of a
parallel evaluation of arguments to a function. This idea in the JavaScript
world is nothing new this actually feels like a pretty recognizable concept. We
want to eval multiple things at the same time and in parallel. Much like a
webworker allows us to do… The notable thing is that this is in 1977, not 2016.
The paper goes further and outlines the approach of taking each of the params
and binding them to a separate process. Here in lies the essential concept, as
soon as we allow individual params to be handled by different processes there is
nothing holding back the evaluation and running of each of params values" in
parallel and in a way that does not block each other. Now if you are like me, you
are thinking why do I care about params, those are just values… however if you
shift your perception of values to something different -- think of params here as
not just flat values but potentially other functions and other "eventual"
values.
In 1995, the Joule language was introduced, intended to be a new model for building distributed systems.
The entire model was that of a making building distributed systems simple. Every action in Joule consisted
of sending and receiving messages from servers. A "channel" was the abstraction or message plumbing through
which the messages are conveyed. It was in this way that Joule set the stage for a formal message pattern.
This idea of a middleman or relay entity that was responsible for delegating async communication instead
of an event based pattern meant that the data and its flow was predictable, which allowed you to treat these
entities as just another pattern for passing data around, even though they were async under the hood.
The culmination of these ideas and the true watershed moment which inspired the
majority of Future/Promise implementations is in E. While E introduced many concepts,
we will only look at a specific subset of the language - the Promise implementation.
E was the first implementation of promises that was truly non-blocking, in that the
work that was being done "Remotely" would never block the future execution.
E also established a lexicon, shown in the diagram above. With a few minor wording
adjustments, this diagram maps 1:1 to what JavaScript promises are today. This
diagram comes from the original E language paper and description by Mark Miller.
You will note that it treats local promises and remote promises as the same thing,
just as Joule treated all messages as "channels" and futures were
potentially going to enable the fully parallel execution across multiple
processes. E builds on these ideas and treats the idea of a Promise as agnostic to
where the work is actually being done. A last interesting note about this original
design is that while the original name for a promise in a failed state was
"broken", which was fine in other languages, this would not work in
JavaScript… why not? "broken" => break … break is a reserved word in
JS… so this was changed to .catch() instead.
Python's Twisted framework directly derived their future/deferred implementation
From the E language. This implementation, while not capturing the entire idea of
E's promises, brought along enough of the concepts to be the seed from which the
JS implementations grew.
From this point forward we will be following the JavaScript track of the story - it is
important to note however that the implementation path in other languages was
running in parallel and in some ways was more "correct" during this time period.
The first implementation in JS that I could find that followed the ethos of E
promises was found in
MochiKit.
MochiKit (extracted from
MochiBot)
by Bob Ippolito, was a direct port from Twisted. When
I reached out to Bob asking where he got the idea for this implementation he
noted that he directly ported the implementation from Python's Twisted lib.
What I find so perfect about our communication is the rationale for why he
ported this functionality (since it really has stood the test of time)
MochiBot used a lot of AJAX style calls and Deferred made that much easier
to work with than directly using XMLHTTPREQUEST
And as he pointed out…
It can't have been that bad of an idea since today's fetch API provides a
HTTP interface that returns a promise.
Keep in mind this was in 2005… this async problem that to this day is
troublesome was being felt and dealt with before I even started seriously
programming.
In
2006
(or perhaps earlier since I think the commit date is lying about the date due to
the bulk import from SVN), the Dojo Toolkit team checked in deferredRequest. So I guess in
this case Dojo did not do it first ;)... But they did do it quite early.
Further searching of the underbelly of the internet yielded additional interesting
finds in Dojo land. In 2007 Alex Russell
decoupled
the Deferred idea from deferredRequest.
In a listserve
post from
2006 Alex also mentioned the inspiration for the deferred request as coming from
Mochikit and Python's Twisted implementation.
Early 2009 was the moment where promise adoption to surged, going from niche to mainstream. While Q may be somewhat familiar to you, there was an
earlier version called Waterken
Q that has a lot of syntax
familiarity with newer libs like modern Q and when.js. The implementation self identified as a
"concise and expressive API for interacting with JSON resources"
In 2009
Kris Zyp
proposed adding Promises
as an official API in the CommonJS mailing this. To many this was the
start of Promises landing in the spec. In his post he notes the influences of
Waterken Q.
In September of
2009
Kris Kowal (also on the Promise API thread) took the ideas of waterken Q along with the design and goals of
E to
create the popular Q lib.
8 days later, Dojo
began the
conversation
to go from the idea of a deferred to the idea of a Promise.
In December
of
2010, deferreds landed in jQuery core, but were not yet exposed externally as
an API. The promise interface was eventually exposed in the $.ajax API - anyone
programming JavaScript at the time most likely has this burned into their memory.
This simple API single-handedly papered over the friction of
XMLHttpRequest,
at the same time introducing the concept of "eventual resolution" as a
happy side-effect.
Keep in mind during this time the web was rapidly moving forward, HTML5 was
coming and more and more new web APIs were shipping: Webcrypto, fetch, IndexedDB,
localStorage. Each of these APIs had to figure out what to do regarding
asynchronous actions. Some opted to use promise-like interfaces, others
callbacks, and some event emitters. This disconnect became especially
complicated when different promise implementations were added to the mix.
The promises spec was simple but the lack of a unified test suite was making
different implementations not work together. Paul
Chavard lead the way with the idea of introducing a
test suite for Promise contract fulfillment. Paul created
a pull request to
Ember to
introduce a Promise
test suite.
Domenic Denicola, recognizing the shortcomings of the implementation, went ahead
and created the A+ promise doc and test suite the day that the Ember PR landed.
(hear him tell the story
here).
This test suite and spec lead to 50+ compatible Promise implementations, a massive
win for the community since it meant that you did not have to worry about different
async / deferred / promise implementations. So long as the implementation was A+
you knew that they would be compatible at the .then level. The meta win here was
the fact that having multiple agreeing implementations showed the TC39 that there
was community consensus around the idea of Promises… an idea that a few years
earlier was thought to be too esoteric for common use.
Because of the community buy-in and eventual need for other async operations
like module loading in ES2015 (called ES6 at the time), Promises were
fast-tracked and landed, with much thanks to Domenic Denicola.
So here we are in 2020 using an API that is the composite of ideas and work
starting in 1961, spanning multiple programming languages and generations.
Special thanks to Kris Kowal, Mark Miller, Rebecca Murphey, Alex Russell, and Domenic Denicola for their
help in reconstructing this timeline. Additional thanks to Karl Horky for the editing help.
Sam Saccone @samccone