Skip to content

Instantly share code, notes, and snippets.

@Sleavely
Last active March 20, 2026 07:37
Show Gist options
  • Select an option

  • Save Sleavely/d5260984187221319950af5b86695489 to your computer and use it in GitHub Desktop.

Select an option

Save Sleavely/d5260984187221319950af5b86695489 to your computer and use it in GitHub Desktop.
Singleton promises
import { describe, it, expect, vi } from 'vitest'
import { Thenable } from './Thenable.js'
describe('Thenable', () => {
class ThenableImplementation extends Thenable {
execute = vi.fn().mockResolvedValue('result')
}
it('resolves when awaited', async () => {
const t = new ThenableImplementation()
const result = await t
expect(result).toBe('result')
})
it('only calls execute once across multiple awaits', async () => {
const t = new ThenableImplementation()
await t
await t
await t
expect(t.execute).toHaveBeenCalledTimes(1)
})
it('reuses the same promise across multiple awaits', async () => {
const t = new ThenableImplementation()
const [r1, r2] = await Promise.all([t, t])
expect(r1).toBe(r2)
expect(t.execute).toHaveBeenCalledTimes(1)
})
it('propagates rejection from execute', async () => {
class FailingThenable extends Thenable {
execute = vi.fn().mockRejectedValue(new Error('boom'))
}
const t = new FailingThenable()
await expect(t).rejects.toThrow('boom')
})
})
/**
* A base class that only executes once, once you `await` it.
* You could call it a Lazy Singleton Promise.
*/
export abstract class Thenable {
#promise: Promise<unknown> | null = null;
abstract execute(): Promise<unknown>;
async then(
resolve: PromiseConstructor['resolve'],
reject: PromiseConstructor['reject']
) {
try {
if (!this.#promise) {
this.#promise = this.execute()
}
resolve(this.#promise);
} catch(error) {
reject(error);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment