Description
Version
16.5.0
Platform
Microsoft Windows NT 10.0.19041.0 x64
Subsystem
stream
What steps will reproduce the bug?
As a result of #39329, a program that works in v16.4.2
no longer works in v16.5.0
.
The issue occurs when an async
writable._final()
implementation explicitly calls the callback argument.
In v16.5.0
the callback-call and async-resolve combined result in [ERR_MULTIPLE_CALLBACK]: Callback called multiple times
in some circumstances.
import { Duplex, Readable } from "stream";
import { setTimeout } from "timers/promises";
const slowDuplex = new Duplex({
read(_size) {
setTimeout(1000).then(() => {
this.push("abc\n");
this.push(null);
})
},
async final(cb) {
// both this `cb()` and the `_final()` Promise resolving
// will trigger `onFinish` under the hood.
cb();
},
});
slowDuplex.on("error", (err) => {
console.log(`"error" fired\n ${err}`);
});
Readable.from([]).pipe(slowDuplex).pipe(process.stdout);
How often does it reproduce? Is there a required condition?
Always
What is the expected behavior?
$ nvm run 16.4.2 repro.mjs
Running node v16.4.2 (npm v7.18.1)
abc
What do you see instead?
$ nvm run 16.5.0 repro.mjs
Running node v16.5.0 (npm v7.19.1)
"error" fired
Error [ERR_MULTIPLE_CALLBACK]: Callback called multiple times
Additional information
Documentation
I don't believe the _final()
documentation sets expectations about the behaviour when it is async
(or a Promise
returner). The source code before and after the change in question seems to point towards it being an intended means of implementing the _final()
.
(Unless I've missed a part of the documentation): Users equipped only with this documentation and without looking at the implementation may be relying on the previous behaviour.
Types
The DefinitelyTyped types could potentially encode this expectation with an overload for void
-returners having the argument, and Promise
-returners not having the argument.