The alternatives look nice. There's just one requirement missing: streaming progress information to the listeners.
I think this completely changes what you have though, in a way that's no longer a primitive? You then have something like an async generator, which is like a fusion of asynchrony and sequence? Except generators are pull not push, so instead you have a promise that accepts a function that operates on a sequence? I don't know if this pattern is common somewhere, someone who does please explain!
I guess you could implement this in JS by having a promise-like structure which returns a tuple containing progress information AND a promise for the remainder of the computation. In a sense, this is similar to the generator approach.
One problem is if your program execs an external program. At what point should a promise kill the external process?