promises are generally designed for events that will certainly occur
I think this is a misconception. The Promise API has the idea of a promise being rejected built into it for cases when the Promise cannot resolve to a desired value.
In the API you described, a Promise would certainly be warranted. If you're worried about the Promise never being settled, your API can offer an optional timeout after which the Promise rejects. Or, if you'd rather let the Promise be cancellable by the caller, returning both the Promise and a cancel method would be appropriate.
The latter might look like:
type CancellableConnection = {
cancel: () => void;
connectionPromise: Promise<Connection>;
};
function establishConnection(opts): CancellableConnection {
let cancelled = false;
return {
cancel: () => { cancelled = true; },
connectionPromise: new Promise((resolve, reject) => {
let connection;
while (!cancelled && !connection) {
// try to connect with exponential backoff
}
if (cancelled) {
reject();
}
resolve(connection);
})
}
}
const { cancel, connectionPromise } = establishConnection(opts);
try {
const timeoutId = setTimeout(cancel, 60000); // cancel after a minute;
const connection = await connectionPromise;
clearTimeout(timeoutId);
// use connection
} catch () {
// handle connection
}
Many details left out, and if timing out is the only case you need to handle then handling timeouts more internal to the the establishConnection
call by racing a setTimeout
-based promise would be simpler.