Execute Program

JavaScript Concurrency: Saving the Resolve Function for Later

Welcome to the Saving the Resolve Function for Later lesson!

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

  • Let's step away from concurrent code for a moment and look at JavaScript's variable scoping rules. In JavaScript, we can declare a variable with let and write to it later.

  • >
    let num;
    num = 3;
    num;
    Result:
    3Pass Icon
  • We can complicate that a bit by declaring the variable, then writing to it from inside a function. The function can write to the variable as long as the variable is in the function's scope.

  • >
    let num;
    function assignNum() {
    num = 3;
    }
    assignNum();
    num;
    Result:
    3Pass Icon
  • We can use the same trick with promises. The promise constructor gets a resolve callback. So far, we've only called it inside the promise.

  • We can also store the resolve function in a variable. This allows us to "capture" the resolve function, then use it outside of the new Promise constructor.

  • >
    let savedResolve;

    const promise = new Promise(resolve => { savedResolve = resolve; });

    /* The `savedResolve` variable now contains the function that we got from the
    * `new Promise` constructor. Calling it will fulfill the promise, even
    * though it now looks like "savedResolve" and "promise" are unrelated. */
    savedResolve(3);

    promise;
    Asynchronous Icon Async Result:
    {fulfilled: 3}Pass Icon
  • (Note that we named our variable savedResolve because otherwise we'd find ourselves writing resolve => { resolve = resolve }. That code would simply assign the argument to itself, rather than assigning it to the let variable in the outer scope.)

  • This is another chance for us to see just how asynchronous promises are. We can:

    1. Store the savedResolve function for later.
    2. Attach thens to the promise (even though we haven't called resolve yet!)
    3. Use setTimeout to wait for a while.
    4. Then call savedResolve, which will fulfill the promise and cause the thens to begin running.
  • >
    let savedResolve;
    const promise = new Promise(resolve => { savedResolve = resolve; })
    .then(n => n * 2);
    setTimeout(() => savedResolve(3), 1000);
    promise;
    Asynchronous Icon Async Result:
    {fulfilled: 6}Pass Icon
  • The net effect of this is: we can split promises into two pieces. We might pass the resolve function to one part of the system, then pass the promise itself to a totally different part. When the "reading" side tries to look inside the promise with a then, it will have to wait until the "writing" side has called resolve.

  • Here's a code problem:

    Use new Promise to assign the promise constructor's resolve function to the variable savedResolve.

    let savedResolve;
    const promise = new Promise(resolve => { savedResolve = resolve; });
    savedResolve(5);
    promise;
    Asynchronous Icon Async Result:
    Goal:
    {fulfilled: 5}
    Yours:
    {fulfilled: 5}Pass Icon
  • A few final notes about the promise constructor. First, we can only resolve a promise once. The promise fulfills with the first value passed to resolve. All other calls to resolve are ignored.

  • >
    function splitPromise() {
    let savedResolve;
    const promise = new Promise(resolve => { savedResolve = resolve; });
    return [savedResolve, promise];
    }

    const [savedResolve, promise] = splitPromise();

    savedResolve(5);
    savedResolve(3);
    savedResolve(1);

    promise.then(n => n * 2);
    Asynchronous Icon Async Result:
    {fulfilled: 10}Pass Icon
  • Second, this lesson shows us why the new Promise constructor's callback is called synchronously. Imagine an alternate universe where the callback is called asynchronously. The line above where we call savedResolve() wouldn't work, because our resolve => { savedResolve = resolve } code wouldn't have run yet!

  • Third, here's a quick example of how we use this technique in Execute Program itself. We've seen examples marked with "async result", which means that they wait up to 3000 ms while asynchronous code is running. But those examples sometimes finish much faster than 3000 ms. That's because we track every promise and timer created as the example runs. If the promises and timers all finish before 3000 ms, we end the example early because there's nothing else to wait for.

  • Here's the top of Execute Program's internal setTimeout function. It does its own internal bookkeeping to track the timer, then later calls the browser's built-in setTimeout. The two lines in the body of this function should look familiar!

  • >
    const promises = [];

    function setTimeout(callback, ms, ...args) {
    /* We store a promise that resolves when the timeout finishes. This lets us
    * wait for an example's timeouts later. */
    let savedResolve;
    promises.push(new Promise(resolve => { savedResolve = resolve; }));

    /* Not shown: code using the browser's built-in `setTimeout` to set up
    * a timer that calls both `callback` and `savedResolve`. */
    }