for people who don't know Node

If you don’t know Node.js yet, having a look at some Node.js or Express.js tutorials/documentation isn’t the worst idea. Two nice ones: Stream Handbook and Express Hello World.

In short: is built around asynchronous streams which emit events when data is available or when a stream is ready to write data. Those streams can be piped into each other, just like streams on the Unix shell (think ls|sort|uniq).

An example from connect-git:

let s = spawn("git", "log", "-100", "--pretty=format:%H|%an|<%ae>|%ad")
  | readlines
  | through2(linesToRecords)
  | through2(recordsToHTML)
  | response

You get the point. All this is running asynchronously and with back-pressure control (that is, if an output stream is busy and can’t take more input, the input stream will pause).

Main Queue

An important part is the concept of ticks. Being JavaScript, in Node.js all user code essentially runs in the main thread. If a component wants to do something later it can call nextTick() or setTimeout().

So why is that kinda good? Well, the user doesn’t have to think as much about concurrency. All user code is running in the same thread. Although parts of a function might run in an interleaved way, they never run at the same time. No need to ‘lock’ stuff to protect against concurrent access etc. has the same concept. Though it is way easier to do threading if you need too (e.g. to offload expensive calculations to another CPU). All event handlers and such of run on a single queue - usually the GCD main-queue.

Pretty much the same thing like with the UIKit/Cocoa main runloop.

So why is that kinda bad? Well, in Node you are kinda running a multiuser server in a single thread. This isn’t as bad as usual because all the I/O is evented and your code won’t block waiting for such. However - if your user code is running an expensive calculation, only this code block is running - blocking everything else. All the sessions of the other users can’t run.

So why isn’t that kinda not so bad in Swift, unlike JavaScript, does support concurrent execution. One can span thread or just use GCD, which handles all the pooling for you. In other words the expensive code can be offloaded to another thread. Of course you then have to deal with concurrency.


let workerQ = DispatchQueue(label      : "de.zeezide", 
                            qos        : .background,
                            attributes : .concurrent)

socket | through2 { chunk, _, end in
  workerQ.async {
    // do something expensive with the chunk
    core.Q.async { end(nil, chunk) } // main Q
} | socket

Final note: In the Q is usually, but does not have to be, the main queue. Refer to the queue using core.Q.


When you are coming from Cocoa or Java you are probably thinking NSInputStream or Sure they are streams, but they are not exactly like streams. streams are a little more general.

There are a few key differences:

a) streams work on any type of Element, not just on UInt8. You can stream strings, you can stream log messages, database records etc etc. Those are all handled the same way! In contrast a only reads bytes. You want to read Unicode characters you need to use a completely separate class, etc.

b) streams emit JavaScript-like events and are non-blocking. If something is available to read - the stream posts an readable event, and only then the client consumes from the stream.

c) streams all have an internal buffer. (no wrapping in BufferedStream)

d) write streams can ‘fill’ and send an drain event if they are ready to accept more content - they are able to limit back pressure.

Most of the ideas come from Node.js. Notably Node has two ‘versions’ of streams:

  • v1 - those are push (‘flowing’) streams, which essentially embed the payload in the event
  • v3 - those are pull streams, they send an event, but then the content is read() from the stream does v3 streams.

OK, this is something you can conceptually (not all streams may be implemented yet) do with streams - everything is completely asynchronous and typesafe:

var byteCount = 0
  | through2 { buf, _, end in byteCount += buf?.length ?? 0; end(nil, buf) }
  | gzip()
  | crypto.createCipher('aes-256-cbc', '0xDEADBEEF')
  | fs.createWriteStream("/tmp/outfile.gz.aes")
  .onFinish("all done!")

You get the idea. The | operators is just an alias for src.pipe(target). The pipe listens for readable events on the source and writes the read data to the target. If the target gets full, the source is paused and an drain event handler is installed. Thus removing back pressure. Etc.

Note: In you can also pipe from regular Swift sequences or Strings. Example:

"Hello World!" 
| spawn("wc", "-l") 
| concat { data in print("\(data)") }