f2
doesn't necessarily know it's being called from f1
, right? You could refactor someday to have f1
call f3
which then calls f2
. At that point it would be unclear whether you want f2
to force the return of f3
or f1
. In general, languages expect that f2
is written not to know exactly where it is being called, or to have deep control over what happens in the calling method: f2
's return value should substitute for the call to f2
without having further control over f1
's execution.
The typical ways of solving this problem are to throw
an exception (which will go up the stack to the point where it is caught, and would be especially reasonable given this seems to be an error case) or to return
a sentinel value that indicates that you should return early. It sounds like falsy values like false
or null
are good candidates, but you've already indicated that this standard approach is unsuitable for your needs.
However, another technique you could use is a continuation passing style, which I suggest because it seems you're fine to edit f2
to take a callback (of which a continuation is a special form). This allows f1
to delegate control to f2
, except that it calls the callback on success rather than failure. This is much more common in asynchronous cases like Promises than in your synchronous case, but it might allow for the flexibility you need in the synchronous case, and it does allow you to delay the call of continuation
if f2
becomes asynchronous. It also allows f1
to choose whether or not to return immediately, and allows for a hypothetical refactored f3
to accept and pass along f1
's continuation or to pass its own (potentially invoking f1
's continuation after further processing).
function f1() {
return f2(a => {
/* the rest of f1 that consumes a */
return ...
});
}
function f2(continuation: (a: TypeOfA) => typeOfF1) {
if (error) {
// f1 returns this immediately, and never has its continuation called.
return ERROR_VALUE;
}
// calculate return value
return continuation(a);
}