TL;DR: creating a Promise splits off another path of execution, and the Promise object represents the end of that path. Calling
.then
adds code to the end of that path.
You can think of your program’s execution as following a piece of yarn. this video illustrates the difference between a synchronous program and the same program using Promises:
Promises let you by explicit about what needs to happen after what, while giving you more flexibility than “each of these things happens one at a time in this order” (the default flow of a simple synchronous program).
The negative is that when you want to specify “do this after that Promise,” you have to package up that code and pass it to .then()
. The Promise object holds the end of the yarn representing its path of execution; .then()
ties more code onto the end and returns the new end.
See this in the readConfig
function, which reads a file and parses its contents. The synchronous version executes on the program’s usual path of execution: readFileSync
retrieves some bits, and then JSON.parse
turns them into a useful object.
In the version with promises, readConfig
returns immediately, but what it returns is the end of a piece of string. It’s a piece of string that includes readFile
, which fetches some bits; tied on by .then()
is JSON.parse,
which turns those bits into a useful object.
The useful object will be available at the end of the orange string to whatever code gets tied on to it later.
Promises beat callbacks in this respect: when you start up the asynchronous task, you don’t have to provide alllll the code that needs to execute after it. You can add more later, as long as you keep hold of the end of the string.
Don’t lose the end of the string! If you don’t need it to add any more code, tie the string off neatly with .catch()
— otherwise an error might come out of a stray end and mess up your program. (I could do another video on that.)
Promises don’t beat callbacks in that you still have to wrap subsequent code up into a function. It gets messy when you have .then()
calls within .then()
calls. But wait! Don’t get discouraged!
In TypeScript and ES2018?, we can write asynchronous code in the same simple format using async
and await
. While the code looks almost the same as the synchronous version, the paths of execution are more like the Promises one.
The async
function returns immediately — don’t be fooled by that return
statement way at the end. It splits off a path of execution, which does work (here, reading the file) until it hits theawait
keyword. The rest of the code (parsing) becomes another piece of string. await
ties the strings together just like .then()
(except way prettier). At the end of an async function
is a return statement, which supplies the value that will come out the end of the string. Anasync
function always returns a Promise.
Promises give you more control, so they give you more to think about. This means they’ll always be more complicated than synchronous code. With async
and await
we get both control and clarity: what Avdi calls “straight line code that just thunks itself onto the work queue whenever it gets stuck.” Don’t fear Promises, do use TypeScript, and do keep hold of the ends of your strings.