Skip to content

Instantly share code, notes, and snippets.

@kumavis
Last active May 4, 2025 01:29
Show Gist options
  • Save kumavis/13cce0056f17bff0bf39cc85d79cd1dc to your computer and use it in GitHub Desktop.
Save kumavis/13cce0056f17bff0bf39cc85d79cd1dc to your computer and use it in GitHub Desktop.
LazyPromise
/**
* A LazyPromise doesn't run its executor until .then, .catch, or .finally is called,
* or the promise is awaited.
* Unfortunately, `@endo/promise-kit`'s isPromise returns false for instances of LazyPromise.
* However, LazyPromise instances will return true for instanceof Promise.
*/
export class LazyPromise extends Promise {
#isListening = false;
#executor;
#resolve;
#reject;
static get [Symbol.species]() {
return Promise;
}
constructor(executor) {
let resolve;
let reject;
super((res, rej) => {
resolve = res;
reject = rej;
});
this.#executor = executor;
this.#resolve = resolve;
this.#reject = reject;
}
#setListening() {
if (this.#isListening) return;
this.#isListening = true;
this.#executor(this.#resolve, this.#reject);
}
then(onFulfilled, onRejected) {
this.#setListening();
return super.then(onFulfilled, onRejected);
}
catch(onRejected) {
this.#setListening();
return super.catch(onRejected);
}
finally(onFinally) {
this.#setListening();
return super.finally(onFinally);
}
}
harden(LazyPromise);
import test from '@endo/ses-ava/prepare-endo.js';
import { isPromise, makePromiseKit } from '@endo/promise-kit';
import { LazyPromise } from '../src/lazy-promise.js';
test('isPromise is false (sad but true)', t => {
const promise = new LazyPromise(() => {});
t.is(isPromise(promise), false);
});
test('instanceof Promise', t => {
const promise = new LazyPromise(() => {});
t.is(promise instanceof Promise, true);
});
test('.then before resolve', t => {
let executorCalled = false;
let resolve;
const promise = new LazyPromise(res => {
resolve = res;
executorCalled = true;
});
t.is(executorCalled, false);
promise.catch(() => {});
t.is(executorCalled, true);
resolve();
t.is(executorCalled, true);
});
test('.catch before resolve', t => {
let executorCalled = false;
let resolve;
const promise = new LazyPromise(res => {
resolve = res;
executorCalled = true;
});
t.is(executorCalled, false);
promise.catch(() => {});
t.is(executorCalled, true);
resolve();
t.is(executorCalled, true);
});
test('.finally before resolve', t => {
let executorCalled = false;
let resolve;
const promise = new LazyPromise(res => {
resolve = res;
executorCalled = true;
});
t.is(executorCalled, false);
promise.finally(() => {});
t.is(executorCalled, true);
resolve();
});
test('await', async t => {
let executorCalled = false;
let resolve;
const promise = new LazyPromise(res => {
resolve = res;
executorCalled = true;
});
t.is(executorCalled, false);
// Make a normal promise kit to control the async function
const { promise: readyP, resolve: ready } = makePromiseKit();
// Kick off an async function that will await the lazy promise
(async () => {
ready();
await promise;
})();
// Wait for the async function to start
await readyP;
t.is(executorCalled, true);
resolve();
});
test('.catch before reject', t => {
let executorCalled = false;
let reject;
const promise = new LazyPromise((res, rej) => {
reject = rej;
executorCalled = true;
});
t.is(executorCalled, false);
promise.catch(() => {});
t.is(executorCalled, true);
reject();
t.is(executorCalled, true);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment