How to Resolve a Promise from Outside in JavaScript

To resolve a promise from outside in JavaScript, assign the resolve callback to a variable defined outside the Promise constructor scope, then call the variable to resolve the Promise. For example:

let promiseResolve;
let promiseReject;

const promise = new Promise((resolve, reject) => {
  promiseResolve = resolve;
  promiseReject = reject;
});

promiseResolve();

Now why would we need to do something like this? Well, maybe we have an operation A currently in progress, and the user wants another operation B to happen, but B must wait for A to complete. Let’s say we have a simple social app where users can create, save and publish posts.

index.html

<!DOCTYPE html>
<html>
  <head>
    <title>Resolving a Promise from Outside</title>
  </head>
  <body>
    <p>
      Save status:
      <b><span id="save-status">Not saved</span></b>
    </p>
    <p>
      Publish status:
      <b><span id="publish-status">Not published</span></b>
    </p>
    <button id="save">Save</button>
    <button id="publish">Publish</button>
    <script src="index.js"></script>
  </body>
</html>
A simple app where users can create, save and publish posts.
Publish doesn’t happen until after save.

What if a post is currently being saved (operation A) and the user wants to publish the post (operation B) while saving is ongoing?. If we don’t want to disable the “Publish” button when the save is happening, we’ll need to ensure the post is saved before publish happens.

index.js


// Enable UI interactivity
const saveStatus = document.getElementById('save-status');
const saveButton = document.getElementById('save');
const publishStatus = document.getElementById(
  'publish-status'
);
const publishButton = document.getElementById('publish');
saveButton.onclick = () => {
  save();
};
publishButton.onclick = async () => {
  await publish();
};

let saveResolve;
let hasSaved = false;

function save() {
  hasSaved = false;
  saveStatus.textContent = 'Saving...';
  setTimeout(() => {
    saveResolve();
    hasSaved = true;
    saveStatus.textContent = 'Saved';
  }, 3000);
}

async function waitForSave() {
  if (!hasSaved) {
    await new Promise((resolve) => {
      saveResolve = resolve;
    });
  }
}

async function publish() {
  publishStatus.textContent = 'Waiting for save...';
  await waitForSave();
  publishStatus.textContent = 'Published';
  return;
}

The key parts of this code are the save() and waitForSave() functions. When the user clicks “Publish”, waitForSave() is called. If the post has already been saved, the Promise returned from waitForSave() resolves immediately, otherwise it assigns its resolve callback to an external variable that will be called after the save. This makes publish() wait for the timeout in save() to expire before continuing.

Post is saved before publish happens.

We can create a Deferred class to abstract and reuse this logic:

class Deferred {
  constructor() {
    this.promise = new Promise((resolve, reject) => {
      this.reject = reject;
      this.resolve = resolve;
    });
  }
}

const deferred = new Deferred();

// Resolve from outside
deferred.resolve();

Now the variables to resolve/reject a Promise and the Promise itself will be contained in the same Deferred object.

We can refactor our code to use this class:

// Enable UI interactivity
// ...

const deferredSave = new Deferred();
let hasSaved = false;

function save() {
  hasSaved = false;
  saveStatus.textContent = 'Saving...';
  setTimeout(() => {
    deferredSave.resolve();
    hasSaved = true;
    saveStatus.textContent = 'Saved';
  }, 3000);
}

async function waitForSave() {
  if (!hasSaved) await deferredSave.promise;
}

async function publish() {
  // ...
}

And the functionality will work as before:

The functionality works as before after the refactor.


Every Crazy Thing JavaScript Does

A captivating guide to the subtle caveats and lesser-known parts of JavaScript.

Every Crazy Thing JavaScript Does

Sign up and receive a free copy immediately.

Leave a Comment

Your email address will not be published. Required fields are marked *