Understanding Promises in JavaScript (with yarn and Legos)

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.

synchronous: one piece of yarn proceeds straight down the code.

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.

promises: the end of a string is returned, an orange piece tied on to a dark blue piece

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.

async function: just as many pieces of yarn as the Promises, with no calls to .then()

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.

Like what you read? Give Jessica Kerr a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.