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.



11 Amazing New JavaScript Features in ES13

This guide will bring you up to speed with all the latest features added in ECMAScript 13. These powerful new features will modernize your JavaScript with shorter and more expressive code.

11 Amazing New JavaScript Features in ES13

Sign up and receive a free copy immediately.

Leave a Comment

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