A few other thoughts:
In just about every programming environment, if you have a mix of synchronous and asynchronous operations (@FFW your "a" is synchronous and "b" is asynchronous), then there is no alternative but to structure the code to handle the asynchronicity. Period.
The question is "Isn't there a way so that I don't have to handle the asynchronicity?"
AFAIK the answer to that question is no. You don't have a choice -- you have to structure the patch accordingly.
"But it seems it can't be done despite PD knows when an object ends."
No, actually, it doesn't. If you have [t b b], and the right outlet includes a [delay] or polls some external result and the result is asynchronous, then the upstream [t] object has no idea when that asynchronous result has completed. (This isn't a Pd limitation -- SuperCollider also requires code to wait explicitly for asynchronous results.)
There are two ways to handle it:
- Put [t . .] before both A and B. B initiates but does not complete. A finishes, and then saves its result in a storage object (cold inlet). Then, when B finishes, it has its own [t] to forward its result and "bang" the storage object.
- Or, put the input into a storage object synchronously, then trigger only B. B's completion then initiates A.
[buddy] just does this for you, in a more general way -- by doing it for you, it's more transparent, easier to read, easier to maintain etc.
"Some of my abstractions have 4 inlets and I have to store and bang each 4 values. Other ones outputs lists I sequence to two inlets, it's a headache to got them in the good order."
If I understand you correctly, that's exactly what Max/cyclone [buddy] is for. Buddy will scale easily up to any 'n' inputs.