Glossary / Writable streams

Writable streams

A WritableStream represents a destination that accepts chunks over time, such as a file, network sink, encoder, or application consumer. Its writer API serializes writes, exposes queue readiness, and supports close and abort so producers can coordinate with sink capacity and lifecycle.

Writer and sink lifecycle

An underlying sink can implement start, write(chunk), close(), and abort(reason). start initializes the sink. Each write may return a promise; the stream waits for it before advancing according to its serialization rules. close indicates successful end of input; abort signals failure or cancellation. getWriter() locks the stream until releaseLock().

const writer = writable.getWriter();
try {
  for (const chunk of chunks) {
    await writer.ready;          // wait for queue capacity
    await writer.write(chunk);   // wait for the write operation
  }
  await writer.close();
} catch (error) {
  await writer.abort(error);
  throw error;
} finally {
  writer.releaseLock();
}

writer.desiredSize reports remaining queue capacity: positive means room, zero means at the high-water mark, negative means the queue is overfull or errored/closed according to state. ready is useful for waiting until backpressure clears. write() settling reflects stream/sink completion semantics, but the sink defines whether that means buffered, transmitted, or durably committed.

Errors, close, and abort

A write rejection errors the stream and causes related operations to reject. Closing after a write failure will generally reject. close() means the producer has finished; abort() requests premature termination and invokes sink cleanup. Neither should be treated as equivalent to a remote transaction commit unless the sink contract promises that. A writer lock should be released in finally so other code can access the stream after the current ownership period.

Engineering guidance

Use a bounded queuing strategy and await capacity for untrusted or fast producers. Define what each successful write guarantees. Ensure abort releases handles and propagates to underlying resources. Avoid fire-and-forget writes: they can create unbounded queues and hide failures. Test slow sinks, rejected writes, close after failure, abort during a pending write, and repeated cleanup.

Interview questions

  1. Why await writer.ready? It lets a producer pause while the queue is under backpressure.
  2. Does write() mean data is durable? Only if the sink's contract says so; stream acceptance alone does not imply durable commit universally.
  3. What does close() mean? No more chunks are expected and the sink should finish normally.
  4. How does abort() differ? It requests premature termination and cleanup, usually because of cancellation or failure.
  5. Why release the writer lock in finally? To avoid leaving the stream inaccessible after success or error.

References