0

I have an event that can occur only once. It may never occur, but we can never be sure that it won't occur anymore.

Specifically, I'm waiting for a connection to be established, but I have a backoff mechanism in place so the connection won't ever truly fail.

Should the connect method return a promise, or should it take a handler and return a give up method, seeing how promises are generally designed for events that will certainly occur? Another argument beside the latter option is that promises that are never resolved are memory leaks.

  • Could you provide a bit of detail on the API? What would the Promise resolution mean? Promises are just async primitives that allow you to do other stuff in the mean time. If you need to wait for the connection to complete, you still need a Promise. – zero298 Jan 20 '21 at 18:10
  • I'm pretty sure that mongoose has a backoff mechanism for connections but still returns a way to deal in Promises to know when you **really did** connect. – zero298 Jan 20 '21 at 18:13
  • 2
    If you want the caller of the `connect` function to have a means of canceling the current operation before it resolves/rejects, then you obviously will need to have some sort of cancel API. That can either be a cancel method on the returned promise or return an object that contains both the promise and a cancel method. Because promises are often chained and custom methods don't necessarily survive the chaining, I would probably return the object that contains both the promise and the cancel method. If this is not what you are asking, please clarify your question. – jfriend00 Jan 20 '21 at 18:27
  • When the cancel method is called before the promise has been resolved, you could then reject the promise. – jfriend00 Jan 20 '21 at 18:36
  • 1
    "*promises that are never resolved are memory leaks*" - [no, absolutely not](https://stackoverflow.com/q/20068467/1048572). – Bergi Jan 20 '21 at 19:57
  • "*Should the `connect` method return a promise*" - yes, definitely. You still can combine this with a way to signal "give up", which would then reject the promise. – Bergi Jan 20 '21 at 19:59

1 Answers1

1

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.

Daniel Raloff
  • 453
  • 3
  • 7
  • Thanks for the detailed explanation. Do you think maybe I could indicate in the API that there's no error by returning a custom thenable but without the second parameter? I'm very keen on indicating exceptions because I'm getting unexpected runtime errors from my own code and I hope to resolve the issue generally by making runtime errors more explicit. – Lőrinc Bethlenfalvy Jan 23 '21 at 17:00
  • On a second thought that's clearly a bad idea because `Promise.all`, `Promise.any`, `Promise.allSettled` and probably 3rd party functions that take a thenable all expect there to be a second parameter on `then`. Do you have any idea how I could still indicate errors more explicitly? – Lőrinc Bethlenfalvy Jan 23 '21 at 17:02
  • If you'd prefer that the promise only reject for errors, you could instead resolve null or undefined. This means more checking on the consumer's end but unlike rejections typescript can ensure that the value is checked before use. If you want to avoid nulls, you might consider resolving a dummy connection using the null object pattern. – Daniel Raloff Jan 24 '21 at 18:13