Skip to content

Readable._construct behaviour contradicts docs #39992

@mikuso

Description

@mikuso

Version

v16.8.0

Platform

Microsoft Windows NT 10.0.19043.0 x64

Subsystem

stream

What steps will reproduce the bug?

Returning a promise from Readable._construct has the same effect as calling the callback. This is not documented and may be a bug?

This leads to the following strange behaviour:

const {Readable} = require('stream');

const stream = new Readable({
    async construct(callback) {
        callback();
    },
    read(size) {
        console.log('read', size);
    },
    destroy(err, callback) {
        console.log('destroy', err);
    }
});

Output:

destroy Error [ERR_MULTIPLE_CALLBACK]: Callback called multiple times
    at new NodeError (node:internal/errors:371:5)
    at onConstruct (node:internal/streams/destroy:255:37)
    at processTicksAndRejections (node:internal/process/task_queues:82:21) {
  code: 'ERR_MULTIPLE_CALLBACK'
}

Similarly:

const {Readable} = require('stream');

const stream = new Readable({
    async construct(callback) {
        // don't call the callback
    },
    read(size) {
        console.log('read');
    }
});

stream.read();

Output:

read

How often does it reproduce? Is there a required condition?

Always

What is the expected behavior?

Flow should not proceed to Readable._read until after the callback passed to Readble._construct has been called.

The docs state:

This optional function will be scheduled in the next tick by the stream constructor, delaying any _read() and _destroy() calls until callback is called.

What do you see instead?

  • In the first example, an error is thrown for calling the callback twice (despite only calling it once)
  • In the second example, flow passes to Readable._read despite the callback not being called yet.

Additional information

It is desirable to be able to be able to use await within Readable._construct, but labelling this function as async (and therefore returning a Promise) appears to cause the callback function to be implicitly called (which is sometimes unwanted).

As a workaround, I can return my own Promise from Readable._construct and use its resolve/reject functions in lieu of the callback.

In summary, either this is expected behaviour and the docs are incorrect - or vice versa. It's hard to tell.

Metadata

Metadata

Assignees

No one assigned

    Labels

    docIssues and PRs related to the documentations.streamIssues and PRs related to the stream subsystem.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions