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
letand write to it later.>
let num;num = 3;num;Result:
3
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:
3
We can use the same trick with promises. The promise constructor gets a
resolvecallback. So far, we've only called it inside the promise.We can also store the
resolvefunction in a variable. This allows us to "capture" theresolvefunction, then use it outside of thenew Promiseconstructor.>
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;Async Result:
{fulfilled: 3}(Note that we named our variable
savedResolvebecause otherwise we'd find ourselves writingresolve => { resolve = resolve }. That code would simply assign the argument to itself, rather than assigning it to theletvariable in the outer scope.)This is another chance for us to see just how asynchronous promises are. We can:
- Store the
savedResolvefunction for later. - Attach
thens to the promise (even though we haven't calledresolveyet!) - Use
setTimeoutto wait for a while. - Then call
savedResolve, which will fulfill the promise and cause thethens to begin running.
- Store the
>
let savedResolve;const promise = new Promise(resolve => { savedResolve = resolve; }).then(n => n * 2);setTimeout(() => savedResolve(3), 1000);promise;Async Result:
{fulfilled: 6}The net effect of this is: we can split promises into two pieces. We might pass the
resolvefunction 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 athen, it will have to wait until the "writing" side has calledresolve.Here's a code problem:
Use
new Promiseto assign the promise constructor'sresolvefunction to the variablesavedResolve.let savedResolve;const promise = new Promise(resolve => { savedResolve = resolve; });savedResolve(5);promise;Async Result:
- Goal:
{fulfilled: 5}- Yours:
{fulfilled: 5}
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 toresolveare 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);Async Result:
{fulfilled: 10}Second, this lesson shows us why the
new Promiseconstructor's callback is called synchronously. Imagine an alternate universe where the callback is called asynchronously. The line above where we callsavedResolve()wouldn't work, because ourresolve => { 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
setTimeoutfunction. It does its own internal bookkeeping to track the timer, then later calls the browser's built-insetTimeout. 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`. */}