Execute Program

JavaScript Concurrency: Promise Constructor

Welcome to the Promise Constructor lesson!

This lesson is shown as static text below. However, it's designed to be used interactively. Click the button below to start!

  • So far, we've created our initial promises with Promise.resolve and Promise.reject. These methods work in many straightforward situations, so it's a good idea to use them.

  • However, there are times when we need more flexibility. For example, we may want a promise to wait for a network, a disk, or a timer. Using what we've seen so far, we might try to call Promise.resolve inside of a setTimeout.

  • >
    function runInOneSecond() {
    setTimeout(() => {
    Promise.resolve()
    .then(() => console.log('then is running'));
    }, 1000);
    }

    console.log('before');
    runInOneSecond();
    console.log('after');
    Asynchronous Icon async console output
  • This approach successfully delayed the promise execution, but we still have a problem. When runInOneSecond starts to run, it only sets a timer, but doesn't create the promise. Then, at the 1000 ms mark, the timer finishes. The promise is created and immediately resolved. The promise resolves inside the setTimeout, so our runInOneSecond function can't return it.

  • If runInOneSecond doesn't return a promise, then runInOneSecond().then(...) won't work. We've lost composability, one of the biggest benefits of promises.

  • (Composability means that we can arrange system components in different ways. In our case, it means that we can chain .then(...) to do more work after runInOneSecond finishes.)

  • Fortunately, it's possible to compose promises with timeouts by using the new Promise constructor. It lets us create a promise now, but resolve it later.

  • >
    function ourCallback(resolve) {
    resolve(5);
    }
    new Promise(ourCallback);
    Asynchronous Icon Async Result:
  • We pass the ourCallback function to the new Promise constructor, which calls it immediately. Our callback gets a resolve function as an argument. When we call resolve, the promise fulfills.

  • Above, we defined our callback separately and passed it in. We did that for clarity, but in most cases the callback is written inline.

  • >
    new Promise(resolve => resolve(5));
    Asynchronous Icon Async Result:
    {fulfilled: 5}Pass Icon
  • We can call resolve() immediately, as in the examples above. However, the real power is in calling it later. This is the opposite approach from what we tried before. Rather than creating a promise inside the setTimeout, we're going to use setTimeout inside the promise.

  • In the next example, we create a promise and attach some thens to it. We then use setTimeout to wait for one second before fulfilling the original promise. Once that second is up, the promise fulfills, so both "downstream" thens also fulfill. This is an important point: when a promise waits for something, all of its thens also have to wait!

  • >
    console.log('before');

    new Promise(resolve => {
    setTimeout(resolve, 1000);
    }).then(() => {
    console.log('first then is running');
    }).then(() => {
    console.log('second then is running');
    });

    console.log('after');
    Asynchronous Icon async console output
  • That solves our problem! We now have a regular-looking promise that fulfills after a delay, and runs its thens once it fulfills.

  • In the example above, we created an artificial delay with setTimeout. However, analogous situations are common in real-world file and network operations, where we write code like:

  • >
    db.getUser(5)
    .then(user => {
    db.updateUser(user, {emailConfirmed: true});
    return user;
    })
    .then(user => {
    // Email the user saying that they're confirmed.
    sendEmailConfirmedMail(user);
    });
  • In code like that, the getUser, updateUser, and sendEmailConfirmedMail functions all ultimately rely on the new Promise constructor that we just saw. They're waiting for network operations instead of timers, but the principle is the same.

  • Here's a code problem:

    Build a new promise with new Promise, putting the value 'it worked' inside it. We've included some code that adds a then to convert the string into SHOUTING! (It's important to celebrate code that works!)

    new Promise(resolve => resolve('it worked'))
    .then(string => string.toUpperCase() + '!');
    Asynchronous Icon Async Result:
    Goal:
    {fulfilled: 'IT WORKED!'}
    Yours:
    {fulfilled: 'IT WORKED!'}Pass Icon
  • We can also use the new Promise constructor to create a rejected promise. To do that, we accept a second argument in our callback, reject.

  • >
    new Promise((resolve, reject) => reject(new Error('it failed')));
    Asynchronous Icon Async Result:
  • >
    new Promise((resolve, reject) => reject(new Error('no such user')));
    Asynchronous Icon Async Result:
    {rejected: 'Error: no such user'}Pass Icon
  • This allows us to accept or reject a promise depending on what happens at runtime.

  • (The next example uses JavaScript's .find array method. It returns the first array element that matches the given function.)

  • >
    const users = [
    {id: 1, name: 'Amir'},
    {id: 2, name: 'Betty'},
    ];

    function getUser(id) {
    return new Promise((resolve, reject) => {
    const user = users.find(user => user.id === id);
    if (user) {
    resolve(user);
    } else {
    reject(new Error('no such user'));
    }
    });
    }
  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    getUser(1);
    Asynchronous Icon Async Result:
    {fulfilled: {id: 1, name: 'Amir'}}Pass Icon
  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    getUser(3);
    Asynchronous Icon Async Result:
    {rejected: 'Error: no such user'}Pass Icon
  • One important thing to keep in mind: the resolve function that we get from the constructor is separate from the Promise.resolve method. And the reject function here is separate from the Promise.reject method.

  • You can think of Promise.resolve and Promise.reject as the simpler versions: when we call Promise.resolve(5), we're creating a new promise that's immediately fulfilled with the value 5. We don't need to pass in a callback function, but the trade-off is that there's also no way for us to delay resolution.

  • The promise constructor is the more complex version: when we do new Promise(resolve => ...), we can delay our call to resolve. We can even save the resolve function and call it from another part of the system, which we'll see in a future lesson.

  • Finally, a quick note about terminology. The (resolve, reject) => ... function that we pass to new Promise is called the "executor". We rarely need to talk about it directly, but if you see "executor" mentioned in discussions of promises, this is why.