Have you ever triggered the same async request multiple times in parallel β like fetching a config or user profile β and wondered:
βWhy are we hitting the server or database more than once when the data is the same?β
This pattern is common across servers and frontends, and itβs inefficient.
Letβs solve that with a super lightweight, zero-dependency, elegant utility:
createSharedPromise() β now optimized with minimal code, closure-based logic, and optional one-time delivery.
π What Does It Do?
β’ Shares one Promise across multiple .then() calls
β’ Deduplicates parallel requests
β’ Caches the result after the first call
β’ Allows clearing the cache
β’ Fully thenable (works with await too)
β’ Optional { once: true } to flush callbacks after resolution
π§ The Code (fully inline, zero-dependencies)
function createSharedPromise(asyncFunction, { once = false } = {}) {
let inFlight = false;
let cache = null;
const fulfillRejectHandlers = [];
const run = (argument, fulfillRejectFlag) => {
if (once) {
while (fulfillRejectHandlers.length) {
const { [fulfillRejectFlag]: handler } = fulfillRejectHandlers.pop();
handler?.(argument);
}
} else {
fulfillRejectHandlers.forEach(
({ [fulfillRejectFlag]: handler }) => handler?.(argument)
);
}
};
return {
clear() {
cache = null;
},
then(onFulfilled, onRejected) {
if (cache !== null) {
onFulfilled?.(cache);
return;
}
fulfillRejectHandlers.push([onFulfilled, onRejected]);
if (inFlight) return;
inFlight = true;
asyncFunction()
.then(result => {
cache = result;
inFlight = false;
run(result, 0);
})
.catch(error => run(error, 1));
}
};
}
This version is tiny, elegant, and avoids unnecessary Promise wrapping or internal get() method exposure.
π§ͺ Try It Live in DevTools
function mockSlowFetch() {
console.log('[mockFetch] Starting...');
return new Promise(resolve =>
setTimeout(() => {
const res = { time: new Date().toISOString() };
console.log('[mockFetch] Done:', res);
resolve(res);
}, 2000)
);
}
const sharedFetcher = createSharedPromise(mockSlowFetch);
console.log('Calling sharedFetcher 3 times:');
sharedFetcher.then(r => console.log('Result 1:', r));
sharedFetcher.then(r => console.log('Result 2:', r));
(async () => {
const r = await sharedFetcher;
console.log('Result 3:', r);
})();
setTimeout(() => {
console.log('Calling again (cached):');
sharedFetcher.then(r => console.log('Result 4 (cached):', r));
}, 3000);
setTimeout(() => {
console.log('Clearing and calling again...');
sharedFetcher.clear();
sharedFetcher.then(r => console.log('Result 5 (new):', r));
}, 6000);
π§© Real-World Use Cases
1. π¦ Vanilla Node.js Server
import http from 'http';
import fs from 'fs/promises';
const configLoader = createSharedPromise(() =>
fs.readFile('./config.json', 'utf-8').then(JSON.parse)
);
http.createServer(async (req, res) => {
if (req.url === '/config') {
configLoader.then(config => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(config));
});
}
}).listen(3000);
2. π§± NestJS Service (shared DB/API calls)
@Injectable()
export class ConfigService {
private configFetcher = createSharedPromise(() =>
this.httpService.get('https://api.example.com/config').toPromise().then(r => r.data)
);
getConfig(): Promise<any> {
return this.configFetcher;
}
refresh() {
this.configFetcher.clear();
}
}
Usage:
await this.configService.getConfig();
3. π§© Angular Service
@Injectable({ providedIn: 'root' })
export class UserService {
private profileFetcher = createSharedPromise(() =>
this.http.get('/api/profile').toPromise()
);
constructor(private http: HttpClient) {}
getProfile(): Promise<any> {
return this.profileFetcher;
}
refreshProfile(): void {
this.profileFetcher.clear();
}
}
Use in component:
this.userService.getProfile().then(profile => this.profile = profile);
4. π Vanilla JS Frontend
<button id="loadConfig">Load Config</button>
<script type="module">
const configFetcher = createSharedPromise(() =>
fetch('/config.json').then(res => res.json())
);
document.getElementById('loadConfig').addEventListener(() => {
configFetcher.then(config => {
console.log('Loaded config:', config);
});
});
</script>
π Optional: { once: true }
If { once: true } is passed, the callback queue will be flushed once, and not retained β useful for cases where memory usage matters, or when only one delivery is expected.
const oneTime = createSharedPromise(() => fetch('/data'), { once: true });
β Benefits Recap
β
Promise deduplication
β
.then() compatible
β
await support (via then)
β
Manual cache clear (.clear())
β
One-time callbacks ({ once: true })
β
Zero dependencies
π§ Final Thoughts
This is an elegant solution for a common problem that can lead to:
β’ Better performance
β’ Lower server/API usage
β’ Cleaner architecture
Use it to wrap:
β’ HTTP calls
β’ DB queries
β’ Config loaders
β’ Feature flags
β’ Anything expensive and cacheable!
Top comments (0)