It’s one of those “cool” things you can do in JavaScript that are actually immensely powerful when put to good use.
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.
<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>
What if the user wants to publish the post when it’s saving?
Solution: Ensure the post is saved before publishing happens.
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;
}
It gets even better when you abstract this logic into a kind of Deferred
class:
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✅:
// ...
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:
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:
// 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.