Coder Social home page Coder Social logo

reactphp / promise-stream Goto Github PK

View Code? Open in Web Editor NEW
112.0 10.0 13.0 107 KB

The missing link between Promise-land and Stream-land for ReactPHP.

Home Page: https://reactphp.org/promise-stream/

License: MIT License

PHP 100.00%
php reactphp promise stream

promise-stream's Introduction

PromiseStream

CI status installs on Packagist

The missing link between Promise-land and Stream-land for ReactPHP.

Table of Contents

Usage

This lightweight library consists only of a few simple functions. All functions reside under the React\Promise\Stream namespace.

The below examples refer to all functions with their fully-qualified names like this:

React\Promise\Stream\buffer(…);

As of PHP 5.6+ you can also import each required function into your code like this:

use function React\Promise\Stream\buffer;

buffer(…);

Alternatively, you can also use an import statement similar to this:

use React\Promise\Stream;

Stream\buffer(…);

buffer()

The buffer(ReadableStreamInterface<string> $stream, ?int $maxLength = null): PromiseInterface<string> function can be used to create a Promise which will be fulfilled with the stream data buffer.

$stream = accessSomeJsonStream();

React\Promise\Stream\buffer($stream)->then(function (string $contents) {
    var_dump(json_decode($contents));
});

The promise will be fulfilled with a string of all data chunks concatenated once the stream closes.

The promise will be fulfilled with an empty string if the stream is already closed.

The promise will be rejected with a RuntimeException if the stream emits an error.

The promise will be rejected with a RuntimeException if it is cancelled.

The optional $maxLength argument defaults to no limit. In case the maximum length is given and the stream emits more data before the end, the promise will be rejected with an OverflowException.

$stream = accessSomeToLargeStream();

React\Promise\Stream\buffer($stream, 1024)->then(function ($contents) {
    var_dump(json_decode($contents));
}, function ($error) {
    // Reaching here when the stream buffer goes above the max size,
    // in this example that is 1024 bytes,
    // or when the stream emits an error.
});

first()

The first(ReadableStreamInterface|WritableStreamInterface $stream, string $event = 'data'): PromiseInterface<mixed> function can be used to create a Promise which will be fulfilled once the given event triggers for the first time.

$stream = accessSomeJsonStream();

React\Promise\Stream\first($stream)->then(function (string $chunk) {
    echo 'The first chunk arrived: ' . $chunk;
});

The promise will be fulfilled with a mixed value of whatever the first event emitted or null if the event does not pass any data. If you do not pass a custom event name, then it will wait for the first "data" event. For common streams of type ReadableStreamInterface<string>, this means it will be fulfilled with a string containing the first data chunk.

The promise will be rejected with a RuntimeException if the stream emits an error – unless you're waiting for the "error" event, in which case it will be fulfilled.

The promise will be rejected with a RuntimeException once the stream closes – unless you're waiting for the "close" event, in which case it will be fulfilled.

The promise will be rejected with a RuntimeException if the stream is already closed.

The promise will be rejected with a RuntimeException if it is cancelled.

all()

The all(ReadableStreamInterface|WritableStreamInterface $stream, string $event = 'data'): PromiseInterface<array> function can be used to create a Promise which will be fulfilled with an array of all the event data.

$stream = accessSomeJsonStream();

React\Promise\Stream\all($stream)->then(function (array $chunks) {
    echo 'The stream consists of ' . count($chunks) . ' chunk(s)';
});

The promise will be fulfilled with an array once the stream closes. The array will contain whatever all events emitted or null values if the events do not pass any data. If you do not pass a custom event name, then it will wait for all the "data" events. For common streams of type ReadableStreamInterface<string>, this means it will be fulfilled with a string[] array containing all the data chunk.

The promise will be fulfilled with an empty array if the stream is already closed.

The promise will be rejected with a RuntimeException if the stream emits an error.

The promise will be rejected with a RuntimeException if it is cancelled.

unwrapReadable()

The unwrapReadable(PromiseInterface<ReadableStreamInterface<T>> $promise): ReadableStreamInterface<T> function can be used to unwrap a Promise which will be fulfilled with a ReadableStreamInterface<T>.

This function returns a readable stream instance (implementing ReadableStreamInterface<T>) right away which acts as a proxy for the future promise resolution. Once the given Promise will be fulfilled with a ReadableStreamInterface<T>, its data will be piped to the output stream.

//$promise = someFunctionWhichResolvesWithAStream();
$promise = startDownloadStream($uri);

$stream = React\Promise\Stream\unwrapReadable($promise);

$stream->on('data', function (string $data) {
    echo $data;
});

$stream->on('end', function () {
    echo 'DONE';
});

If the given promise is either rejected or fulfilled with anything but an instance of ReadableStreamInterface, then the output stream will emit an error event and close:

$promise = startDownloadStream($invalidUri);

$stream = React\Promise\Stream\unwrapReadable($promise);

$stream->on('error', function (Exception $error) {
    echo 'Error: ' . $error->getMessage();
});

The given $promise SHOULD be pending, i.e. it SHOULD NOT be fulfilled or rejected at the time of invoking this function. If the given promise is already settled and does not fulfill with an instance of ReadableStreamInterface, then you will not be able to receive the error event.

You can close() the resulting stream at any time, which will either try to cancel() the pending promise or try to close() the underlying stream.

$promise = startDownloadStream($uri);

$stream = React\Promise\Stream\unwrapReadable($promise);

$loop->addTimer(2.0, function () use ($stream) {
    $stream->close();
});

unwrapWritable()

The unwrapWritable(PromiseInterface<WritableStreamInterface<T>> $promise): WritableStreamInterface<T> function can be used to unwrap a Promise which will be fulfilled with a WritableStreamInterface<T>.

This function returns a writable stream instance (implementing WritableStreamInterface<T>) right away which acts as a proxy for the future promise resolution. Any writes to this instance will be buffered in memory for when the promise will be fulfilled. Once the given Promise will be fulfilled with a WritableStreamInterface<T>, any data you have written to the proxy will be forwarded transparently to the inner stream.

//$promise = someFunctionWhichResolvesWithAStream();
$promise = startUploadStream($uri);

$stream = React\Promise\Stream\unwrapWritable($promise);

$stream->write('hello');
$stream->end('world');

$stream->on('close', function () {
    echo 'DONE';
});

If the given promise is either rejected or fulfilled with anything but an instance of WritableStreamInterface, then the output stream will emit an error event and close:

$promise = startUploadStream($invalidUri);

$stream = React\Promise\Stream\unwrapWritable($promise);

$stream->on('error', function (Exception $error) {
    echo 'Error: ' . $error->getMessage();
});

The given $promise SHOULD be pending, i.e. it SHOULD NOT be fulfilled or rejected at the time of invoking this function. If the given promise is already settled and does not fulfill with an instance of WritableStreamInterface, then you will not be able to receive the error event.

You can close() the resulting stream at any time, which will either try to cancel() the pending promise or try to close() the underlying stream.

$promise = startUploadStream($uri);

$stream = React\Promise\Stream\unwrapWritable($promise);

$loop->addTimer(2.0, function () use ($stream) {
    $stream->close();
});

Install

The recommended way to install this library is through Composer. New to Composer?

This project follows SemVer. This will install the latest supported version:

composer require react/promise-stream:^1.7

See also the CHANGELOG for details about version upgrades.

This project aims to run on any platform and thus does not require any PHP extensions and supports running on legacy PHP 5.3 through current PHP 8+ and HHVM. It's highly recommended to use the latest supported PHP version for this project.

Tests

To run the test suite, you first need to clone this repo and then install all dependencies through Composer:

composer install

To run the test suite, go to the project root and run:

vendor/bin/phpunit

License

MIT, see LICENSE file.

promise-stream's People

Contributors

carusogabriel avatar clue avatar jsor avatar kelunik avatar lucasnetau avatar nhedger avatar reedy avatar simonfrings avatar wyrihaximus avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

promise-stream's Issues

Why the first() method does not resolve the promise upon "end" event?

Hi,
I'm trying to use rakdar/reactphp-csv inside an amphp/amp application and I would like to yield one CSV row at a time. To do so I use this package to transform data events of rakdar/reactphp-csv to ReactPHP promises which in turns are then converted to AMP promises. So I can yield CSV rows, one at a time. Here follows a simple test script:

<?php

require 'vendor/autoload.php';

\Amp\Loop::defer(function () {
    $csv = fopen('https://raw.githubusercontent.com/Rakdar/reactphp-csv/master/examples/country-codes.csv', 'rb');
    $stream = new \React\Stream\ReadableResourceStream($csv, \Amp\ReactAdapter\ReactAdapter::get());
    $reader = new \Rakdar\React\Csv\Reader($stream);

    while ($row = yield \Interop\Amp\Promise\adapt(\React\Promise\Stream\first($reader, 'data'))) {
        echo $row[10] .PHP_EOL;
    }
});

\Amp\Loop::run();

This code works but when the stream reaches the end it throws a "Stream closed" exception.
If I add the following inside the first function it works without exceptions:

$stream->on('end', function () use ($resolve) {
    $resolve(null);
});

So, Why the first() method does not resolve the promise upon "end" event?
What do you think?

all() function fails when a specified event doesn't pass any data

Hi!
For all() function the docs say:

The promise will resolve with an array of whatever all events emitted or null if the events do not pass any data.

Here is a simple example. I create a writable stream and then try to collect data from all drain events. These events don't provide any data. As a result the promise should resolve with null:

$writable = new \React\Stream\WritableResourceStream(fopen('php://stdout', 'w'), $loop, 1);
$writable->write('Hello world');
React\Promise\Stream\all($writable, 'drain')->then(function() {
    echo 'drained' . PHP_EOL;
});

But I receive

Notice: Undefined variable: data in vendor/react/promise-stream/src/functions.php on line 129

Looks like in source code for all() function we should add default null value for variable $data?

$bufferer = function ($data = null) use (&$buffer) {
        $buffer []= $data;
    };

Next steps for PromiseStream with ReactPHP v3

We're currently moving forward with working on ReactPHP v3 and releasing the roadmap tickets for all our components (see reactphp/event-loop#271 and others). We still have some components that we haven't finalized plans for, especially with the next major version approaching. It's important to address how we can make sure these components are aligned with the upcoming ReactPHP v3.

We already opened a discussion for PromiseStream in https://github.com/orgs/reactphp/discussions/475 2 years ago, and with ReactPHP v3 around the corner, it's time to decide what to do here. To quote @clue from said ticket:

The way I see it, the project is only used for its buffer() function for the most part (in fact, this is also why this package was born: reactphp/stream#45). If you take a look at the installation stats, you'll see that close to 100% of all installations come from reactphp/http. To make matters worse, the HTTP component has to work around some of the edge cases of buffering closed streams, so it might as well just implement 10 lines of code without an additional dependency.

I will argue that installing an entire project for a single function only is a bit overkill (for the PHP ecosystem at least). Likewise, maintaining an entire project requires a non-trivial amount of work – which I'd rather redirect towards our main components.

To add insult to injury, the package also features poorly-named first() and all() functions. This interferes with IDE autocompletion, as our Promise component also offers a prominent all() function from a different namespace. Better names might perhaps be something along the lines of bufferString() and bufferArray(). But renaming would incur a BC break, so we'd either have to wait for vNEXT or spend even more effort and add additional functions and deprecate the existing ones.

There was a discussion about moving the ReactPHP PromiseStream component to Friends of ReactPHP, or if we simply reuse the logic elsewhere and deprecate the component afterwards together with the EOL of ReactPHP v1.

Happy about input on this, so let's discuss possible options and decide on what makes the most sense 🚀

Roadmap to stable v1.0.0

Proposed roadmap to stable:

v0.1.0 ✅

  • Released 2016-05-10
  • Tag current state of this package

v0.1.1 ✅

  • Released 2017-05-15
  • Update to support the latest stream tags

v0.1.2 ✅

  • Released 2017-10-18
  • Support maximum buffer size

v1.0.0 ✅

  • Released 2017-10-24
  • No new changes, this merely marks the previous release as "stable"

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.