JavaScript Concurrency: Control Flow With Promises
Welcome to the Control Flow With Promises lesson!
This lesson is shown as static text below. However, it's designed to be used interactively. Click the button below to start!
Promises are JavaScript objects, which are always "truthy". If we try to use a promise in a boolean context, like an
ifcondition or the!operator, it will always act liketrue, even if it contains a "falsey" value.>
const amir = Promise.resolve({name: 'Amir'});!amir;Result:
>
const amir = Promise.resolve({name: 'Amir'});const nobody = Promise.resolve(undefined);[!amir, !nobody];Result:
[false, false]
>
const nobody = Promise.resolve(undefined);let bool;if (nobody) {bool = true;} else {bool = false;}bool;Result:
true
There's no way to synchronously look inside a promise, so writing
if (somePromise)never makes sense. Instead, we have to put our conditionals, loops, etc. inside ofthencallbacks.>
const amir = Promise.resolve({name: 'Amir'});amir.then(user => {if (user === undefined) {return 'no user';} else {return 'user exists';}});Async Result:
{fulfilled: 'user exists'}>
const amir = Promise.resolve(undefined);amir.then(user => {if (user === undefined) {return 'no user';} else {return 'user exists';}});Async Result:
{fulfilled: 'no user'}This combines well with another concept we've seen: promises resolving to other promises. For example: when a
thencallback returns a promise, the "outer" promise gets the same fulfilled value as the "inner" promise.>
const promise1 = Promise.resolve(1);const promise2 = Promise.resolve(undefined).then(() => {return promise1;});promise2;Async Result:
{fulfilled: 1}We can use that to write conditionals inside of a
thencallback, returning different promises in different situations.>
Promise.resolve({name: 'Amir'}).then(user => {if (user === undefined) {return new Promise(resolve => {setTimeout(() => resolve('no user'), 500);});} else {return new Promise(resolve => {setTimeout(() => resolve('user exists'), 500);});}});Async Result:
{fulfilled: 'user exists'}This has a nice side effect that might not be immediately obvious. Above, we returned an explicit promise inside of our
thencallback. But we can also return any other value. Since thethencallback's result is always wrapped in a promise, we'll get a promise back either way!The example below introduces
getUserNameandgetUserDisplayNamefunctions to the code we saw earlier.>
function getUserName(id) {// In a real system we'd query from a database.const users = {1: 'Amir',};return new Promise(resolve => {setTimeout(() => {const user = users[id];resolve(user);}, 500);});}function getUserDisplayName(userIdPromise) {return userIdPromise.then(userId => {if (userId === undefined) {// Return a bare string that will be wrapped in a promise.return 'no user';}// Return a promise wrapping the user's name.return getUserName(userId);});}- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
getUserDisplayName(Promise.resolve(1));Async Result:
{fulfilled: 'Amir'} - Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
getUserDisplayName(Promise.resolve(undefined));Async Result:
{fulfilled: 'no user'} Here's a code problem:
Write a
publishDateForDisplayfunction. Its job is to return the publish date for a blog post, formatted as a string, wrapped in a promise. We've provided aformatDatefunction, so you won't need to call any methods on the date objects.publishDateForDisplaytakes a date wrapped in a promise, likePromise.resolve(publishDate). If the date isundefined, it should return the string'unpublished'. Otherwise it should return the result offormatDate(publishDate).function formatDate(date) {// Turn the date object into a string like '2024-11-19'return [date.getFullYear(), date.getMonth() + 1, date.getDate()].join('-');}function publishDateForDisplay(publishDatePromise) {return publishDatePromise.then(publishDate => {if (publishDate === undefined) {return 'unpublished';} else {return formatDate(publishDate);}});}// Format one actual date and one undefined date.const date1 = new Date('November 19, 2024 00:00:00');const date2 = undefined;publishDateForDisplay(Promise.resolve(date1)).then(formatted1 =>publishDateForDisplay(Promise.resolve(date2)).then(formatted2 =>[formatted1, formatted2]));Async Result:
- Goal:
{fulfilled: ['2024-11-19', 'unpublished']}- Yours:
{fulfilled: ['2024-11-19', 'unpublished']}
It's good to understand all of this, but these code examples probably feel awkward. They are! This promise code is cleaner than the equivalent callback-based code, but it's still not as clean as regular synchronous code. In a different lesson, we'll see how async/await cleans this up, making it look almost like regular synchronous code.