question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. ItĀ collects links to all the places you might be looking at while hunting down a tough bug.

And, if youā€™re still stuck at the end, weā€™re happy to hop on a call to see how we can help out.

Do not call ".end()" in the callback of ".write"

See original GitHub issue

Hey šŸ‘‹

Iā€™ve got a question about the way this library calls the original .end() method on the ClientRequest. It does so in the callback of .write() (given there is data to send so that the callback gets called):

https://github.com/follow-redirects/follow-redirects/blob/13136e95bbe23cabbeaeb74bd0c933aa98dd9b96/index.js#L127-L130

I think I understand the intention here: send this chunk, when done (regardless of whether itā€™s successful or notā€”because youā€™re not checking the error callback argument) mark the request as ready.

Question

Can we execute .write and .end in parallel?

this.write(data, encoding, function () {
  self._ended = true;
});

// This call is moved out of the write's callback.
currentRequest.end(null, null, callback);

this._ending = true;

Context

Iā€™m a maintainer of the API mocking library called Mock Service Worker. We intercept requests in Node.js using the @mswjs/interceptors library, which augments the native ClientRequest class. We use class augmentation instead of stubbing it to allow as much of the Node.js default behavior to execute without having to polyfill it. Since we are an API mocking tool, we cannot really write request body chunks until we know thereā€™s (or isnā€™t) a request handler for this particular request. Thatā€™s why we only collect body chunks in the .write() method:

https://github.com/mswjs/interceptors/blob/219f9f3cd82f5baf3297f7c6095caa356c148d31/src/interceptors/ClientRequest/NodeClientRequest.ts#L85-L97

We then write those chunks only for passthrough requests when the request handler is known. That is possible to check only in the .end method:

https://github.com/mswjs/interceptors/blob/219f9f3cd82f5baf3297f7c6095caa356c148d31/src/interceptors/ClientRequest/NodeClientRequest.ts#L170-L179

Now since RedirectableRequest only calls .end in the callback of .write, when our library is applied the .end is never called. That happens because we do not call the .write callbacks immediately (not semantic, no chunks are being written just yet so it feels wrong to confirm the result by calling a callback), but instead are referred to the .end. Note that this difference is purely an implementation detail which should have no effect on the end-consumer. For them, the expected result is intercepted request and mocked response.

Common usage of .end

Iā€™ve also noticed that commonly the .write and .end are called in parallel:

const req = http.get()
req.write('hello world')
req.end()

That is the way to send request body chunk and end a request in about any example I can find. Iā€™ve never seen the write(chunk, () => this.end()) usage before but I understand it must have reasons behind it.

What Iā€™d like to know

  1. What are the reasons to call .end only in the callback of .write?
  2. What are the implications of calling these two methods in parallel?
  3. Do you have any recommendations on how to handle your flow in our ā€œinterceptorsā€ library?

Thank you for looking into this.

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Reactions:16
  • Comments:11

github_iconTop GitHub Comments

1reaction
RubenVerborghcommented, May 23, 2022
  • Write callback(s) have already been called. Now itā€™s either we call them again upon the actual chunk write, or ignore them entirely to prevent duplicate calls.

Youā€™ve fulfilled the contract; the write callbacks have been called.

  • If we choose to ignore them, we donā€™t know whether writing them to the actual socket connection succeeded (the server mightā€™ve rejected a chunk). And itā€™s only in the .write() call where we are exposed the error argument of the callback to know that.

There will be no errors here, because none occur at that stage. Any errors will be emitted as an error event on the stream or through the end callback, and thatā€™s fine.

1reaction
RubenVerborghcommented, Apr 20, 2022

Do you see any potential issues if we call write callbacks while the request hasnā€™t actually written anything yet?

No; from the perspective of the direct consumer, it is written: itā€™s just that your writing is a memory buffer. Itā€™s success, so the callback should be invoked.

What matters is finding the proper solution

I think both work, but I think changing the interceptor is the conceptually correct and safest one.

Read more comments on GitHub >

github_iconTop Results From Across the Web

node.js - Can Callback for stream.write Reliably Verify That ...
write() method writes some data to the stream, and calls the supplied callback once the data has been fully handled. If an error...
Read more >
Callback Hell
The cause of callback hell is when people try to write JavaScript in a way where execution happens visually from top to bottom....
Read more >
Writing Functions - Node-RED
The Function node allows JavaScript code to be run against the messages that are passed through it. The message is passed in as...
Read more >
Q&A for Telemarketers & Sellers About DNC Provisions in TSR
The National Do Not Call Registry is a list of phone numbers from consumers who have indicated their preference to limit the telemarketing...
Read more >
Stream | Node.js v18 API
Previous calls to write() may not have drained, and may trigger an ERR_STREAM_DESTROYED error. Use end() instead of destroy if data should flush...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found