JavaScript Concurrency: Promise Then
Welcome to the Promise Then lesson!
This lesson is shown as static text below. However, it's designed to be used interactively. Click the button below to start!
We've seen a way to write a sequential series of
setTimeouts. (We can also do this with any other callback-based asynchronous functions, likesetInterval, which we'll see later in this course.) SequentialsetTimeouts become difficult to read and debug as we add more steps:>
setTimeout(() => {console.log('0.5 seconds have passed');setTimeout(() => {console.log('1.0 second has passed');setTimeout(() => {console.log('1.5 seconds have passed');}, 500);}, 500);}, 500);That code is difficult to read because there's a lot of nesting. We can imagine a function that automatically waits for the previous timer to finish. We could call it
then:>
setTimeout(() => { console.log('0.5 seconds have passed'); }, 500);then(() => { console.log('1.0 second has passed'); }, 500);then(() => { console.log('1.5 seconds have passed'); }, 500);We can't write exactly that function. However, JavaScript's
Promisefeature gets close to that syntax. Promises allow us to chain asynchronous operations in a clean, composable way. They also help us to avoid the deep nesting that we saw above.Let's temporarily forget about concurrency, and instead start with some very normal-looking JavaScript:
>
let value = 5;value = value * 3;value = value + 1;value;Result:
16
We'll transform this code to put each step in its own function, then chain them together. The code will remain synchronous, but this will help us understand what promises do.
First, we can rewrite the assignments as small functions. If we immediately call each function, they'll have the same effect as the assignments above.
>
let value = 5;value = (n => n * 3)(value);value = (n => n + 1)(value);value;Result:
16
That chain only involved two functions calls, but we can add more.
>
let value = 5;value = (n => n * 3)(value);value = (n => n + 1)(value);value = (n => n * 2)(value);value;Result:
32
There's a repeated pattern here:
value = someFunction(value). We'll introduce achainfunction to contain that pattern for us. Here's a first pass at that function:>
function chain(value) {return {value: value,};}chain(5);Result:
{value: 5}chainreturns an object, so we can access that object's properties immediately using dot notation.- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
chain(5).value;Result:
5
Next, we add a
thenmethod, which takes our callback function (the thing we want to do to our value) as an argument. We'll pass the callback in likesomeChain.then(n => n * 3).Our
thenmethod should return the new value: in this case,n * 3. Think aboutthenlike this: "whatever came before me happened,thenthis function is going to happen".>
function chain(value) {return {value: value,then: callback => {return callback(value);}};}Note that just like
value,thenis a property on the objectchainreturns. Just as we accessedchain(5).valueabove, we can also dochain(5).then(callback)to call the callback function.- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
chain(5).then(n => n * 3);Result:
15
Now, the final step: we change
then's return value. Instead of returning a value like5directly, we wrap the return value in a new chain. Now thechainfunction lives up to its name: when we call.then, we get a new chain, and we can call.thenon that, which gives us a new chain, etc. When we're done, we access.valueHere's the full
chainfunction, followed by some example uses of it:>
function chain(value) {return {value: value,then: callback => {return chain(callback(value));}};}- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
chain(5).then(n => n * 3).value;Result:
15
- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
chain(5).then(n => n * 3).then(n => n + 1).value;Result:
16
Our
chainfunction is still synchronous because it always runs the callback functions right away, rather than deferring them until later. Still, it shows us the basic interface of promises: there's a way to create an object containing a value, and there's a way to addthencallbacks that return new values.Now we can switch to real promises, where the equivalent of
chain(someValue)isPromise.resolve(someValue). For now, you can think ofPromise.resolveas "create a promise that contains the given value". (We'll explore the subtle details of what "resolve" actually means in later lessons!)>
Promise.resolve(11).then(n => n + 1).then(n => n / 2).then(n => console.log(n));async console outputThere are a total of four promises constructed in that example: the initial
Promise.resolvecall returns a promise, and each of thethencalls also returns a promise. Just like ourchainfunction, each promise has athenmethod. However, there's novalueproperty for us to call. Instead, we can access the value directly inside thethenfunction.If you look at a promise in your browser's built-in console, you'll see something like this. (There might be some variation depending on your browser.)
>
Promise.resolve(5);Result:
The "Promise" and "5" parts are easy: it's a promise with a
5inside.The "fulfilled" part means that the promise actually contains a value. Later, we'll see promises that are waiting for something else to happen ("pending") and promises where something has gone wrong ("rejected"). For now, all that matters is that every "fulfilled" promise contains a value.
We'll be working with promises often, so a quick note on how our interface treats them. Execute Program shows promises similarly to how web browsers do. However, unlike browsers, we need to let you type promises in as the answers to our code examples. The
Promise {<fulfilled>: 5}syntax that we saw above would be awkward to type out. Instead, we format fulfilled promises as simple JavaScript objects.>
Promise.resolve(5);Async Result:
>
Promise.resolve(5).then(n => n + 1);Async Result:
{fulfilled: 6}Here's a code problem:
Add another
thencall to wrap the promise's string value in an array.Promise.resolve(5).then(n => n * 2).then(n => n.toString()).then(n => [n]);Async Result:
- Goal:
{fulfilled: ['10']}- Yours:
{fulfilled: ['10']}