writable.end()

Signal that no more data will be written, with an optional final chunk.

Signature
writable.end([chunk[, encoding]][, callback])
  • Added in Node.js v0.9.4

Parameters

ParameterTypeDescription
$chunkoptionalstring | Buffer | TypedArray | DataView | anyOptional data to write. For streams not operating in object mode, chunk must be a string, Buffer, TypedArray or DataView. For object mode streams, chunk may be any JavaScript value other than null.
$encodingoptionalstringThe encoding if chunk is a string.
$callbackoptionalFunctionCallback for when the stream is finished.

Return value

thisThe writable stream itself, allowing calls to be chained.

Errors

Calling write() after end() raises ERR_STREAM_WRITE_AFTER_END, and calling end() a second time raises ERR_STREAM_ALREADY_FINISHED.

Changelog

  • PHP v22.0.0The chunk argument can now be a TypedArray or DataView instance (also in v20.13.0).
  • PHP v15.0.0The callback is now invoked before 'finish' or on error, so code that assumed 'finish' ran first sees the opposite order.
  • PHP v14.0.0The callback is invoked if 'finish' or 'error' is emitted.
  • PHP v10.0.0This method now returns a reference to writable instead of undefined.
  • PHP v8.0.0The chunk argument can now be a Uint8Array instance.

end() tells a writable stream that nothing further will be written. The stream flushes whatever is buffered, runs _final() if the implementation provides one, and then emits 'finish'. The optional chunk is written immediately before closing, which is why res.end('done') and file.end(lastLine) are so common.

When to reach for it

Any time you drive a writable by hand. A file stream that is never ended leaves buffered data unflushed and the descriptor open; an HTTP response that is never ended leaves the client waiting until it times out. If you used pipeline() or pipe(), the destination is ended for you and calling end() yourself is a bug.

What trips people up

end() is not synchronous. It schedules the shutdown; the data is not durable and the file is not closed when the call returns. Read the file on the next line and you may get nothing. Wait for the callback, the 'finish' event, or stream.finished().

Since Node 15 the end() callback fires before 'finish', reversing the order older code relied on. And 'finish' means the stream accepted everything, not that 'close' has fired — if you need the file descriptor released, listen for 'close' or use finished().

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Close a log file after the last line so its contents are flushed to disk
  • Terminate an HTTP response with a final body chunk
  • Signal end-of-input to a compressor so it can emit its trailer
  • Release a file descriptor in a script that writes a report and exits

Frequently asked questions

How do I close a writable stream in Node.js?

Call writable.end(), optionally passing a final chunk and a callback; the stream flushes its buffer, runs _final(), then emits 'finish'. Do not call it on a destination that pipe() or pipeline() is managing, since they end it for you.

Why is my file empty right after calling end()?

Because end() is asynchronous — it schedules the flush rather than performing it, so a read on the next line races the write. Wait for the end() callback, the 'finish' event, or await stream.finished(writable).

What is the difference between the finish and close events?

'finish' fires when end() has been called and all data has been handed to the underlying system; 'close' fires later, once the stream and any file descriptor are actually released. Listen for 'close' when you need the resource freed.

More examples

Awaiting the close of a file you just wrote

The commented line is the classic bug: end() only schedules the flush, so a synchronous read straight afterwards races the write and often prints an empty string. Awaiting finished() resolves once 'finish' and 'close' have both happened, so the read is guaranteed to see "line one\nline two\n".

Example · javascript
import fs from 'node:fs';
import { finished } from 'node:stream/promises';

const out = fs.createWriteStream('report.txt');
out.write('line one\n');
out.end('line two\n');

// WRONG: end() has not flushed yet, this may read an empty file.
// console.log(fs.readFileSync('report.txt', 'utf8'));

// Right: wait for the stream to be fully finished and closed.
await finished(out);
console.log(fs.readFileSync('report.txt', 'utf8'));

fs.rmSync('report.txt');

Why you should not call end() inside a pipeline

pipeline() and pipe() both end the destination when the source finishes, so a manual end() afterwards raises ERR_STREAM_ALREADY_FINISHED. The commented option at the bottom is the supported way to keep a destination open across several pipelines — it is available on the promise form of pipeline().

Example · javascript
import { pipeline } from 'node:stream/promises';
import { Readable } from 'node:stream';
import fs from 'node:fs';

const dest = fs.createWriteStream('out.txt');

await pipeline(Readable.from(['a', 'b'], { objectMode: false }), dest);

// pipeline() already ended the destination.
console.log('ended by pipeline:', dest.writableEnded); // true

try {
  dest.end(); // ERR_STREAM_ALREADY_FINISHED
} catch (err) {
  console.log('second end:', err.code);
}

// To keep the destination open for more writes, opt out explicitly:
//   await pipeline(source, dest, { end: false });

Discussion

  • Be the first to comment on this lesson.