Skip to content

Instantly share code, notes, and snippets.

@wolfv
Forked from igorw/gist:4108347
Created November 19, 2012 13:28
Show Gist options
  • Save wolfv/4110633 to your computer and use it in GitHub Desktop.
Save wolfv/4110633 to your computer and use it in GitHub Desktop.
React v0.2.4 release notes

React v0.2.4 is more awesome

DNS promises

This release has two major improvements. The first one is that react/dns is now using a promise based API. In case you missed it, react/promise is a PHP library for promises. Take a look at the README and familiarize yourself with it, as it will be used by many libs in the react ecosystem, including core.

The previous API was:

    $dns->resolve('igor.io', function ($ip) {
        echo "Yay, the IP is $ip.\n";
    });

The new API is:

$dns->resolve('igor.io')->then(function ($ip) {
    echo "Yay, the IP is $ip.\n";
});

This does not seem like a big change, but it's quite significant. Because it allows that promise to be passed around and chained together.

$dns
    ->resolve('igor.io')
    ->then(function ($ip) {
        $sink = new BufferedSink();
        $conn = new Connection(stream_socket_client($ip.':1337'));
        $conn->pipe($sink);

        return $sink->promise();
    })
    ->then(function ($contents) {
        echo "Result: $contents\n";
    });

By returning a promise at the end of each function, you can chain the then calls. The next function in the chain will only be called once the previous one is resolved.

Another important point about promises is error handling. The second argument to then is an optional error handler, which provides a consistent way of handling async errors. For any async operation that results in one return value, you should use a promise based API.

Stream primitives

The second new feature in this release is the addition of some basic stream primitives that you can extend. Promises are a great API for eventual return values, but they only work for single values. The real strength of async I/O is processing a stream of data. Such programs should be implemented as streams that parse the data they receive and emit their result on the other end. These programs should communicate via pipes.

With the newly added primitives it is a lot easier to build such streams. The following classes were added to react/stream:

  • ReadableStream
  • WritableStream
  • CompositeStream
  • ThroughStream
  • BufferedSink

ReadableStream

This is a basic implementation of the ReadableStreamInterface. It is useful if you want to provide methods that result in something being emitted. For example a chat client using an imaginary chat protocol:

class ChatClient extends ReadableStream
{
    public function say($message)
    {
        $this->emit('data', [sprintf("GLOBAL:%s\n", $this->escape($message))]);
    }

    public function privateMessage($user, $message)
    {
        $this->emit('data', [sprintf("PRIVATE:%s:%s\n", $this->escape($user), $this->escape($message))]);
    }

    private function escape($input)
    {
        return str_replace(["\n", ':'], ['\\n', ''], $input);
    }
}

$client->pipe($conn);

$client->say('Hello everyone!');
$client->privateMessage('cboden', "Dude, what's up?");

WritableStream

This is great for creating sinks. A sink is a writable stream that consumes data and forms the end of a pipe chain. Usually it will emit objects on the other side, so it translates from streams to something else. An excellent use case for this is any sort of parser. Just override the write method to consume the provided data in any way you want.

class ChatParser extends WritableStream
{
    private $buffer = '';

    public function write($data)
    {
        $chunk = $this->buffer.$data;
        if (false !== strpos($chunk, "\n")) {
            $frames = explode("\n", $chunk);
            $chunk = array_pop($frames);
            foreach ($frames as $frame) {
                $this->parseFrame($frame);
            }
        }
        $this->buffer = $chunk;
    }

    private function parseFrame($frame)
    {
        list($action, $tail) = explode(':', $frame, 2);
        $args = explode(':', $tail);

        $this->emit(strtolower($action), $args);
    }
}

$parser = new ChatParser();
$parser->on('global', function ($user, $message) {
    echo "Received global message from $user:\n";
    echo "$message\n";
});

$conn->pipe($parser);

CompositeStream

The CompositeStream allows you to combine a readable and a writable stream into a single object which implements both readable and writable stream interfaces. For example combining the previous client and parser streams to a single one:

$client = new ChatClient();
$parser = new ChatParser();
$chat = new CompositeStream($client, $parser);

$conn->pipe($chat)->pipe($conn);

ThroughStream

The through stream acts as a filter on the data it receives. It receives data and emits other data. By default it just echoes everything it receives, to customize it, override the filter method. Here's an example of a shout filter, which uppercases anything it receives:

class ShoutFilter extends ThroughStream
{
    public function filter($data)
    {
        return strtoupper($data);
    }
}

$shout = new ShoutFilter();
$conn->pipe($shout)->pipe($conn);

That would be a TCP echo server that just shouts right back at any connected client.

BufferedSink

The BufferedSink is basically an async version of stream_get_contents. It is interesting because it bridges between the stream world and the promise world. It buffers anything it receives until the input stream closes, at which point the promise it provides is resolved.

Here's an example of getting the entire output of an HTTP request, implemented at the TCP level:

$sink = new BufferedSink();

$conn->write("GET / HTTP/1.1\r\n");
$conn->write("\r\n");
$conn->pipe($sink);

$sink
    ->promise()
    ->then(function ($data) {
        return str_replace("\r\n", "\n", $data);
    })
    ->then(function ($data) {
        echo "Received HTTP response:\n";
        echo $data;
    });

This one can hopefully also be re-used quite a bit within core.

Thanks

Thanks to @jsor for react/promise and for bringing it into react/dns.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment