Execute Program

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 if condition or the ! operator, it will always act like true, 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]Pass Icon
  • >
    const nobody = Promise.resolve(undefined);
    let bool;

    if (nobody) {
    bool = true;
    } else {
    bool = false;
    }

    bool;
    Result:
    truePass Icon
  • 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 of then callbacks.

  • >
    const amir = Promise.resolve({name: 'Amir'});
    amir.then(user => {
    if (user === undefined) {
    return 'no user';
    } else {
    return 'user exists';
    }
    });
    Asynchronous Icon Async Result:
    {fulfilled: 'user exists'}Pass Icon
  • >
    const amir = Promise.resolve(undefined);
    amir.then(user => {
    if (user === undefined) {
    return 'no user';
    } else {
    return 'user exists';
    }
    });
    Asynchronous Icon Async Result:
    {fulfilled: 'no user'}Pass Icon
  • This combines well with another concept we've seen: promises resolving to other promises. For example: when a then callback 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;
    Asynchronous Icon Async Result:
    {fulfilled: 1}Pass Icon
  • We can use that to write conditionals inside of a then callback, 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);
    });
    }
    });
    Asynchronous Icon Async Result:
    {fulfilled: 'user exists'}Pass Icon
  • This has a nice side effect that might not be immediately obvious. Above, we returned an explicit promise inside of our then callback. But we can also return any other value. Since the then callback's result is always wrapped in a promise, we'll get a promise back either way!

  • The example below introduces getUserName and getUserDisplayName functions 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)
    );
    Asynchronous Icon Async Result:
    {fulfilled: 'Amir'}Pass Icon
  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    getUserDisplayName(
    Promise.resolve(undefined)
    );
    Asynchronous Icon Async Result:
    {fulfilled: 'no user'}Pass Icon
  • Here's a code problem:

    Write a publishDateForDisplay function. Its job is to return the publish date for a blog post, formatted as a string, wrapped in a promise. We've provided a formatDate function, so you won't need to call any methods on the date objects.

    publishDateForDisplay takes a date wrapped in a promise, like Promise.resolve(publishDate). If the date is undefined, it should return the string 'unpublished'. Otherwise it should return the result of formatDate(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]
    )
    );
    Asynchronous Icon Async Result:
    Goal:
    {fulfilled: ['2024-11-19', 'unpublished']}
    Yours:
    {fulfilled: ['2024-11-19', 'unpublished']}Pass Icon
  • 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.