-
Notifications
You must be signed in to change notification settings - Fork 13
Description
Wanted to flag this “gotcha” that I've encountered a few times.
Case 1 - calling abort() before calling something that attaches a reader to the stream:
(async () => {
const s = new Stream();
s.aborted().catch(() => ({})); // Swallow abort exception
s.write(1); // This won't be pumped until toArray() is called below
s.abort(); // The write above is now doomed!
s.end();
console.log(await s.toArray()); // Promise of s.write(1) will now reject as toArray() attempts to pull from aborted source
})();
Case 2 - calling abort() while the promise-like argument of write() has yet to resolve:
(async () => {
const s = new Stream();
s.aborted().catch(() => ({})); // Swallow abort exception
const p = s.toArray(); // Attach reader before abort(), to avert case 1
s.write(delay(2).then(() => 1)); // Wait 2 ms before resolving 1
s.abort(); // No more writes past this point; but the promise generated by `delay(2).then(() => 1)` will try to write in the future, and fail
s.end();
console.log(await p);
})();
The first, to me, seems like a bug. write() was clearly called before abort().
The second one is less clear... According to the specification for abort(), this write should fail because it can no longer be processed downstream.
But it creates a problem for the source, which must not only detect abort() and stop writing, but also handle any unresolved promises it already passed downstream (presumably by catching the "aborted" errors from the corresponding write calls).
I don't really have a fix in mind for case 2. I considered simply ignoring promise resolutions in this case, but that violates the guarantee that writes must either succeed and pass the value to the reader or fail. I also considered modifying abort() to wait for resolution of promises sent to write(), but that would violate the expectation that abort() is supposed to be synchronous.
Maybe the best thing to do is to document the gotcha. I would also suggest that instead of post-abort writes throwing the abort error, they should throw a "WriteAfterAbortError" that wraps that error. This would make it easier to distinguish from the case where the source's aborted() rejection was not handled.