Resolve a promise from outside in JavaScript: practical use cases

It’s one of those “cool” things you can do in JavaScript that are actually immensely powerful when put to good use.

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

Powerful practical use cases

Action B waiting for action A

A is ongoing but the user wants to do B but A needs to happen first.

Example: Social app where users can create, save, and publish posts. Like Medium.

HTML
<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>
A simple app where users can create, save and publish posts.
Publish doesn’t happen until after save.

What if the user wants to publish the post when it’s saving?

Solution: Ensure the post is saved before publishing happens.

JavaScript
saveButton.onclick = () => { save(); }; publishButton.onclick = async () => { await publish(); }; let saveResolve; let hasSaved = false; async function save() { hasSaved = false; saveStatus.textContent = 'Saving...'; // ✅ Resolve promise from outside await makeSaveRequest(); saveResolve(); hasSaved = true; saveStatus.textContent = 'Saved'; } 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; }
Post is saved before publish happens.

It gets even better when you abstract this logic into a kind of Deferred class:

JavaScript
class Deferred { constructor() { this.promise = new Promise((resolve, reject) => { this.reject = reject; this.resolve = resolve; }); } } const deferred = new Deferred(); // Resolve from outside deferred.resolve();

Refactoring✅:

JavaScript
// ... const deferredSave = new Deferred(); let hasSaved = false; async function save() { hasSaved = false; saveStatus.textContent = 'Saving...'; // ✅ Resolve promise from outside await makeSaveRequest(); saveResolve(); hasSaved = true; saveStatus.textContent = 'Saved'; } async function waitForSave() { if (!hasSaved) await deferredSave.promise; } async function publish() { // ... }

And it works exactly like before:

The functionality works as before after the refactor.

Deferred is much cleaner, which is why we’ve got tons of NPM libraries like it: ts-deferred, deferred, promise-deferred

Promisifying an event stream

It’s a great setup I’ve used multiple times.

Doing an async task that’s actually waiting for an event stream to fire, internally:

JavaScript
// data-fetcher.js const deferred = new Deferred(); let dataDeferred; function startListening() { dataDeferred = new Deferred(); eventStream.on('data', (data) => { dataDeferred.resolve(data); }); } async function getData() { return await dataDeferred.promise; } class Deferred { constructor() { this.promise = new Promise((resolve, reject) => { this.reject = reject; this.resolve = resolve; }); } } // client.js const { startListening, getData } = require('./data-fetcher.js'); startListening(); const data = await getData();

Final thoughts

Resolving promises externally unlocks powerful patterns.

From user actions to event streams, it keeps your code clean and flexible. Consider libraries like ts-deferred for even better handling.



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 *