Asynchronous coroutines for PHP 7.



Build Status Code Coverage Code Quality Latest Version

An asynchronous coroutine kernel for PHP 7.

composer require recoil/recoil

The Recoil project comprises the following packages:

  • recoil/api - The public Recoil API for application and library developers.
  • recoil/dev - Development and debugging tools.
  • recoil/recoil (this package) - A reference implementation of the kernel described in the API.
  • recoil/react - A kernel implementation based on the ReactPHP event loop.
  • recoil/kernel - Common components used to implement the kernels.


Recoil aims to ease development of asynchronous applications by presenting asynchronous control flow in a familiar "imperative" syntax.

What does that mean? Let's jump right in with an example that resolves multiple domain names concurrently.

use Recoil\React\ReactKernel;
use Recoil\Recoil;

function resolveDomainName(string $name, React\Dns\Resolver\Resolver $resolver)
    try {
        $ip = yield $resolver->resolve($name);
        echo 'Resolved "' . $name . '" to ' . $ip . PHP_EOL;
    } catch (Exception $e) {
        echo 'Failed to resolve "' . $name . '" - ' . $e->getMessage() . PHP_EOL;

ReactKernel::start(function () {
    // Create a React DNS resolver ...
    $resolver = (new React\Dns\Resolver\Factory)->create(
        yield Recoil::eventLoop()

    // Concurrently resolve three domain names ...
    yield [
        resolveDomainName('', $resolver),
        resolveDomainName('', $resolver),
        resolveDomainName('probably-wont-resolve', $resolver),

This code resolves three domain names to their IP address and prints the results to the terminal. You can try the example yourself by running the following command in the root of the repository:


Run it a few times. You'll notice that the output is not always in the same order. This is because the requests are made concurrently and the results are shown as soon as they are received from the DNS server.

Note that there is no callback-passing, and that regular PHP exceptions are used for reporting errors. This is what we mean by "familiar imperative syntax".

Clear as mud? Read on :)



Coroutines are essentially functions that can be suspended and resumed while maintaining their state. This is useful in asynchronous applications, as the coroutine can suspend while waiting for some task to complete or information to arrive, and the CPU is free to perform other tasks.

PHP generators provide the language-level support for functions that can suspend and resume, and Recoil provides the glue that lets us use these features to perform asynchronous operations.

A Recoil application is started by executing an "entry-point" generator, a little like the main() function in the C programming language. The Recoil kernel inspects the values yielded by the generator and identifies an operation to perform. For example, yielding a float with value 30 causes the coroutine to suspend execution for 30 seconds.

The DNS example above shows a rather more advanced usage, including concurrent execution and integration with asynchronous code that is not part of Recoil. The resulting code, however, is quite normal looking, except for the yield statements!

Within Recoil, the term coroutine specifically refers to a PHP generator that is being executed by the Recoil kernel. It's no mistake that generators can be used in this way. Nikita Popov (who is responsible for the original generator implementation in PHP) published an excellent article explaining generator-based coroutines. The article even includes an example implementation of a coroutine scheduler, though it takes a somewhat different approach.


A Strand is Recoil's equivalent to your operating system's threads. Each strand has its own call-stack and may be suspended, resumed, joined and terminated without affecting other strands. The elements on the call-stack are not regular functions, but are instead coroutines.

Unlike threads, execution of a strand can only suspend or resume when a coroutine specifically requests to do so, hence the term cooperative multitasking.

Strands are very light-weight and are sometimes known as green threads, or (perhaps less correctly) as fibers.

Recoil's concept of the strand is defined by the Strand interface.

Dispatchable Values

An Dispatchable Value is any value that Recoil recognises when yielded by a coroutine. For example, yielding another generator pushes that generator onto the current strand's call-stack and invokes it, thus making it a coroutine.

The Recoil facade class describes the complete list of supported values.

The Kernel and Kernel API

The kernel is responsible for creating and scheduling strands, much like the operating system kernel does for threads.

The kernel and strands are manipulated using the kernel API, which is a set of standard operations defined in the Recoil API and accessible using the Recoil facade.

There are multiple kernel implementations available. This repository contains a stand-alone implementation based on stream_select(). The recoil/react package provides a kernel based on the ReactPHP event-loop.


The following examples illustrate the basic usage of coroutines and the kernel API. Additional examples are available in the examples folder.

References to Recoil and ReactKernel refer to the Recoil facade, and the React kernel implementation, respectively.

Basic execution

The following example shows the simplest way to execute a generator as a coroutine.

    function () {
        echo 'Hello, world!' . PHP_EOL;

ReactKernel::start() is a convenience method that instantiates the React-based kernel and executes the given coroutine in a new strand. Yielding null (via yield with no explicit value) allows PHP to parse the function as a generator, and allows the kernel to process other strands, though there are none in this example.

Calling one coroutine from another

A coroutine can be invoked by simply yielding it, as described in the section on coroutines above. You can also use the yield from syntax, which may perform better but only works with generators, whereas yield works with any dispatchable value.

function hello()
    echo 'Hello, ';

function world()
    echo 'world!' . PHP_EOL;

ReactKernel::start(function () {
    yield hello();
    yield world();

Returning a value from a coroutine

To return a value from a coroutine, simply use the return keyword as you would in a normal function.

function multiply($a, $b)
    yield; // force PHP to parse this function as a generator
    return $a * $b;
    echo 'This code is never reached.';

ReactKernel::start(function () {
    $result = yield multiply(2, 3);
    echo '2 * 3 is ' . $result . PHP_EOL;

Throwing and catching exceptions

One of the major syntactic advantages of coroutines over callbacks is that errors can be reported using familiar exception handling techniques. The throw keyword can be used in in a coroutine just as it can in a regular function.

function multiply($a, $b)
    if (!is_numeric($a) || !is_numeric($b)) {
        throw new InvalidArgumentException();

    yield; // force PHP to parse this function as a generator
    return $a * $b;

ReactKernel::start(function() {
    try {
        yield multiply(1, 'foo');
    } catch (InvalidArgumentException $e) {
        echo 'Invalid argument!';
  • When array of coroutines is yielded, exception in one could lead to incorrect state

    When array of coroutines is yielded, exception in one could lead to incorrect state

    I am currently investigating this problem. My code:

    public function warmup(): \Generator
        $this->logger->info('Warming up active proxy cache');
        $active = $this->cache->getItem('');
        $activeList = $active->isHit() ? $active->get() : [];
        $toCheck = array_diff($this->proxyProvider->getProxies(), $activeList);
        $this->logger->debug('Total active proxies: ' . count($activeList) . ', to check: ' . count($toCheck));
        while (count($toCheck) > 0) {
            $batch = array_slice($toCheck, 0, static::BATCH_SIZE);
            $toCheck = array_slice($toCheck, static::BATCH_SIZE);
            yield array_map(function (Proxy $proxy) {
                return function () use ($proxy) {
                    $client = $this->clientBuilder->getProxifiedClient($proxy, static::CLIENT_TIMEOUT);
                    try {
                        yield $client->get('', [
                            'User-Agent' => UserAgent::random(),
                        ])->then(function ($ret) {
                            echo 1;
                    } catch (\Exception $e) {
                        throw $e;
            }, $batch);
        return count($activeList);

    If async request fails, I get this:

    In StrandTrait.php line 134:
      Call to a member function throw() on null
    opened by nick4fake 14
  • PHP 8.0 Support

    PHP 8.0 Support

    • [x] Blocked by
    • [x] Blocked by (fixed in v1.0.2)
    • [x] Blocked by (fixed in v1.0.5)
    • [x] Blocked by (fixed in v0.4.1)
    • [x] Blocked by (fixed in v1.0.3)
    opened by jmalloc 11
  • Function not working concurrently

    Function not working concurrently

    Hi there,

    I'm new to recoil and using it with React. I'm trying to run functions concurrently, not synchronously.

    This is my code to test:

        public function execute($json)
            $loop = Factory::create();
            $kernel = ReactKernel::create($loop);
            $kernel->execute(function () {
                yield [
        private function fetchTrade($id) {
            echo $id . PHP_EOL;

    Unfortunately the code runs synchronously and not concurrently (the sleep is called after each execution). How do I correct this code so it does work concurrently like in the example?

    Thanks so much, Maarten

    opened by factormaarten 10
  • Working with event emitters

    Working with event emitters

    Thanks for the timer example, @jmalloc.

    Event emitters

    I've implemented an HTTP server in this Gist. To help with event emitters, I created an EventCoroutine that transforms event data. While in this case I'm only listening to one event (viz. request, whose event data is wrapped in Transaction), one could listen to multiple events, return different classes, and handle them accordingly.

    There are two things I notice:

    • I currently fetch the event loop in httpServer(). Is there no way to push this into Server::listen()?
    • I notice that while running this example, my CPU does to full load. What am I doing wrong?


    One-off callbacks

    As for working with one-off callbacks, I would just wrap them in promises, which are wrapped by the coroutine adapter.

    $dfd = new Deferred;
    $loop->addTimer(1, [$dfd, 'resolve']);
    return $dfd->promise();
    opened by rjkip 10
  • Recoil asynchronous framework

    Recoil asynchronous framework

    What's the difference between using ReactPHP by itself and using Recoil? If I was building a new asynchronous application, would I put Recoil on top of ReactPHP? What kind of advantages does it provide?

    opened by CMCDragonkai 7
  • Implement a reference kernel.

    Implement a reference kernel.

    This should be a simple loop implementation that satisfies the Kernel interface, implemented using the traits provided in the Recoil\Kernel namespace.

    It should be unit tested, and use functional tests from the recoil/dev package.

    opened by jmalloc 3
  • Implement coroutine instrumentation and strand stack traces.

    Implement coroutine instrumentation and strand stack traces.

    • [x] Instrument coroutines to trace function name and yield points
    • [x] Create a stack trace based on the coroutines in the strand rather than the kernel internals
    • [x] Implement an autoloader that automatically instruments code
    • [ ] Emulate real PHP stack frames (include class, object, type, etc)
    • [x] Move the implementation to recoil/dev once working
    opened by jmalloc 3
  • `Api::resume()` and `throw()` make it difficult to integrate with sync code.

    `Api::resume()` and `throw()` make it difficult to integrate with sync code.

    The intent of these methods was to provide a way to resume strands as part of the kernel API, so that it may be versioned independently of the underlying strand implementation (#124). However, using these operations (exclusively) means that you can't resume strands from outside a coroutine.

    Long story short, there needs to be a way to resume strands from "regular PHP code" that is part of the public API. Strand already implements Listener, so perhaps this is as simple as packaging and versioning Listener along with Api.

    FYI @koden-km

    opened by jmalloc 3
  • Support

    Support "async-interop" interfaces.

    Blocked: there are no stable releases of the async-interop packages.

    The async-interop organisation provides some interfaces that seek to improve interoperability of async libraries.

    Recoil should provide support for each of them:

    • [ ] Interop\Async\Loop\Driver - As of async-interop/event-loop#87, Driver is an abstract class, so Kernel can not simply be a driver with additional features. Instead we can:
      • Provide a Kernel -> Driver adaptor
      • Provide a Driver-based kernel, much like ReactKernel (see #148)
      • ~~The native kernel could implement this interface directly. I think this is preferable than having the Kernel interfae extend Driver, as other implementations may have a more direct way of implementing Driver~~.
    • [ ] Interop\Async\Loop\DriverFactory
      • I'm not sure what's best here, perhaps simply having separate implementations for creating a ReactKernel vs a NativeKernel.
    • [ ] Interop\Async\Awaitable\Awaitable
      • Should be yieldable (ie, supported by Api::dispatch())
      • Could possibly be extended by Recoil's own Awaitable interface, with some renaming.
      • Could possibly be extended by Strand
      • Handling errors requires use of the "global loop accessor" to push errors to the current loop implementation, which might conflict or bypass recoil's own event handler? Need to look into it further.
    • [ ] Interop\Async\Promise\Promise
      • Should be yieldable (ie, supported by Api::dispatch())
      • Retain support for then method detection, but favour this interface so that we can use done() when we know its available
    opened by jmalloc 3
  • Recoil::execute when/how can it be used?

    Recoil::execute when/how can it be used?

    Hi James,

    I am still experimenting with my Database layer and trying to write async DataObject code in a synchronous way.

    I wrote some test code show below, but it doesn't do what I would expect. I am starting to wonder if I really understand generators at all (functions that can generate values multiple times AFAIK?). Anyway I wrote the code below as I was seeing Recoil::execute functions not being called at all when called from Ratchet messages. I am trying to simulate the same thing here using a timer. Interestingly Timer 2 never fires at all, can you explain why that is? Is there something obvious I am missing here?



    use Recoil\Kernel\Strand;
    use Recoil\React\ReactKernel;
    use Recoil\Recoil;
    use React\EventLoop\Factory;
    $loop = \React\EventLoop\Factory::create();
    $kernel = new ReactKernel($loop);
    $kernel->execute( function () use ($loop, $kernel) {
        $i = 0;
        $loop->addPeriodicTimer(0.1, function() use (&$i) {
            echo '1 Timer'.++$i, PHP_EOL;
        $j = 0;
        $timer = $loop->addPeriodicTimer(0.1, function() use (&$j, $kernel) {
            print '2Timer'.++$j. PHP_EOL;
            $strand = yield Recoil::execute(function () {
                print 'Async 1' . PHP_EOL;
            $strand2 = yield Recoil::execute(function ()  {
                print 'Async 2' . PHP_EOL;
        yield 0;
    opened by djsharman 3
  • Improved / more intuitive stream API.

    Improved / more intuitive stream API.

    Currently, the only stream related operations in the kernel API are read() and write(), which are essentially the same as \fread() and \fwrite(), except that they allow other strands to execute while waiting for stream activity.

    While these methods are useful, they do have some limitations:

    1. Any given strand can only wait for a single stream, for a single purpose (read or write).
    2. They don't provide a way to block until all data is written, or the read buffer is filled.

    ~~To address these issues, I think it best that read() and write() are removed from the kernel API entirely, and replaced with a single select() operation.~~

    (1) can be solved by the addition of Api::select(), which would essentially be analogous to \stream_select() but allowing other strands to execute in the meantime. Multiple strands can wait for the same streams, but only one of them is resumed when the stream becomes ready. select() would also be used for channels when they are reimplemented, allowing a strand to wait for any combination of streams or channels ~~(perhaps strands could use a FIFO to signal the underlying event-loop when they are ready)~~ Guh, this is not necessary.

    (2) can be solved by changing the read() and write() implementations to convenience methods for reading and writing until the buffer is filled or drained, respectively. Additionally, these methods could also "lock" the individual streams so that no other strands that are using select(), read() or write() can "interject" and read/write data from/to the middle of the buffer. The existing functionality can be obtained by using select() followed by \fread() and/or \fwrite() as appropriate.

    I think this will make for a very usable interface for the common case, without sacrificing any flexibility.

    opened by jmalloc 3
  • PRs from non-organization members fail due to missing GITHUB_TOKEN variable.

    PRs from non-organization members fail due to missing GITHUB_TOKEN variable.

    Travis CI (for obvious reasons) does not define encrypted variables when building a PR from a non-member.

    The GITHUB_TOKEN variable is only used to avoid GitHub API rate-limiting affecting Composer's ability to install dependencies.

    The build process needs to be changed to use composer --prefer-source ... when GITHUB_TOKEN is empty.

    opened by jmalloc 0
  • Signal handling.

    Signal handling.

    Blocked by #85

    Unlike streams, signal handlers need to be registered in an event-emitter like manner, otherwise signals could be missed if they arrive before the handler is re-registered (unless we always register handlers for all signals and buffer them, not sure of the performance impact there).

    This could be accomplished with Recoil::callback() + pcntl_signal(), but it would be better if the kernel dispatched signals, probably when select() and equivalent is interrupted, as well as on a short-ish timer.

    opened by jmalloc 0
  • pthreads


    Look into how pthreads might provide advantages for CPU-bound application. It may be possible to use expose workers and synchronisation primitives in co-routine-familiar Recoilish way.

    opened by jmalloc 0
  • 1.0.2(Mar 6, 2022)

  • 1.0.1(Apr 5, 2018)

  • 1.0.0(Oct 17, 2017)

  • 1.0.0-alpha.2(Jan 9, 2017)

  • 1.0.0-alpha.1(Dec 16, 2016)


    As of this version, the recoil/recoil package only contains the "reference kernel", which is an implementation of the kernel with no external dependencies. As such, it can not be used to execute ReactPHP code.

    The ReactPHP kernel is still available in the recoil/react package.

    Libraries and applications should be developed against the interfaces and classes provided by recoil/api. The intent is to keep these interfaces as stable as possible across the various kernel implementations and versions.

    • [BC] Moved the ReactPHP-based kernel to the recoil/react package
    • [BC] Moved the public interfaces to the recoil/api package
    • [BC] Moved kernel implementation details to the recoil/kernel package
    Source code(tar.gz)
    Source code(zip)
  • 0.6.0(Jul 29, 2016)

    • [BC] Revert addition of Api::resume() and throw() (added in 0.5.2)
    • [BC] Added Strand::trace() and setTrace() methods
    • [NEW] Api::suspend() now accepts an optional terminator callback, which is invoked if the strand is terminated before it is resumed
    • [NEW] Added StrandTrace interface, a low-level strand observer to be used by debugging tools
    • [FIXED] Strand::send() and throw() no longer fail if the strand has already exited
    Source code(tar.gz)
    Source code(zip)
  • 0.5.2(Jul 15, 2016)

  • 0.5.1(Jul 12, 2016)

  • 0.5.0(Jul 11, 2016)


    This is the first release that requires PHP 7. The internals have been rewritten from the ground up. Some features available in previous releases are no longer available as substitute functionality has not yet been added.

    There are far too many changes to list here individually, however much of the kernel API remains the same.

    • [BC] Channels and streams have been removed from the core package.
    • [BC] Recoil::run() has been removed (see ReactKernel::start()).

    Kernel API changes:

    • [BC] kernel() has been removed.
    • [BC] eventLoop() is only available when using ReactKernel.
    • [BC] return_() has been removed, as generators can return values in PHP 7.
    • [BC] throw_() has been removed.
    • [BC] finally_() has been removed.
    • [BC] noop() has been removed.
    • [BC] stop() has been removed.
    • [BC] select() now operates on PHP streams, rather than strands.
    • [NEW] Added read() and write()
    • [NEW] Added callback()
    • [NEW] Added link() and unlink()
    • [NEW] Added adopt()
    • [NEW] Added any(), some() and first()
    Source code(tar.gz)
    Source code(zip)
  • 0.4.0(Mar 6, 2016)

    This is the final release that will operate with PHP 5. In an effort to work towards a production ready 1.0 release, future releases will require PHP 7.

    • [BC] Dropped Interface suffix from interfaces
    • [BC] Renamed ReadableStream to ReadablePhpStream
    • [BC] Renamed WritableStream to WritablePhpStream
    • [BC] Renamed CoroutineAdaptor to StandardCoroutineAdaptor
    • [BC] Renamed KernelApi to StandardKernelApi
    • [BC] Renamed Strand to StandardStrand
    • [BC] Renamed StrandFactory to StandardStrandFactory
    • [NEW] Added support for Guzzle promises
    • [IMPROVED] The callback given to Recoil::suspend is now optional
    Source code(tar.gz)
    Source code(zip)
  • 0.3.0(Jun 26, 2015)

    • [BC] Removed StrandInterface::resume()
    • [NEW] return statement can be used to return a value inside a coroutine (requires PHP 7)
    • [IMPROVED] Improved method documentation on Recoil facade (thanks @rjkip)
    Source code(tar.gz)
    Source code(zip)
  • 0.2.1(Oct 15, 2014)

  • 0.2.0(Sep 23, 2014)

    To faciliate several performance improvements the following backwards compatibility breaking changes have been introduced:

    • [BC] CoroutineInterface no longer implements EventEmitterInterface - several unused events were fired every time a co-routine was called
    • [BC] Recoil::finalize() now only works with generated based co-routines - this was previously implemented using the aforementioned events
    Source code(tar.gz)
    Source code(zip)
  • 0.1.0(Feb 4, 2014)

Coroutine-based asynchronous programming for PHP 7.
Promises/A implementation for PHP.

Promise A lightweight implementation of CommonJS Promises/A for PHP. The master branch contains the code for the upcoming 3.0 release. For the code of

ReactPHP 2.2k Jan 3, 2023
Promises/A+ library for PHP with synchronous support

Guzzle Promises Promises/A+ implementation that handles promise chaining and resolution iteratively, allowing for "infinite" promise chaining while ke

Guzzle 7.3k Jan 3, 2023
Asynchronous coroutines for PHP 7.

Recoil An asynchronous coroutine kernel for PHP 7. composer require recoil/recoil The Recoil project comprises the following packages: recoil/api - T

Recoil 787 Dec 8, 2022
λ Run PHP Coroutines & Fibers as-a-Service on the AWS Lambda.

λ Swoole Runtime for AWS Lambda Run PHP Coroutines & Fibers as-a-Service on the AWS Lambda. Getting started Create your Lambda function index.php <?ph

Leo Cavalcante 32 Dec 29, 2022
True coroutines for PHP>=8.1 without worrying about event loops and callbacks.

Moebius Pure coroutines for PHP 8.1. To promises and callbacks needed. Just pure parallel PHP code inside coroutines. Moebius Band: A loop with only o

Frode Børli 204 Dec 21, 2022
True coroutines for PHP>=8.1 without worrying about event loops and callbacks.

Moebius Pure coroutines for PHP 8.1. No promises and callbacks needed. Just pure parallel PHP code inside coroutines. Moebius Band: A loop with only o

Moebius for PHP 141 Jun 16, 2022
An asynchronous event driven PHP socket framework. Supports HTTP, Websocket, SSL and other custom protocols. PHP>=5.3.

Workerman What is it Workerman is an asynchronous event-driven PHP framework with high performance to build fast and scalable network applications. Wo

walkor 10.2k Jan 4, 2023
An asynchronous event driven PHP socket framework. Supports HTTP, Websocket, SSL and other custom protocols. PHP>=5.3.

Workerman What is it Workerman is an asynchronous event-driven PHP framework with high performance to build fast and scalable network applications. Wo

walkor 10.2k Dec 31, 2022
True asynchronous PHP I/O and HTTP without frameworks, extensions, or annoying code. Uses the accepted Fibers RFC to be implemented into PHP 8.1

PHP Fibers - Async Examples Without External Dependencies True asynchronous PHP I/O and HTTP without frameworks, extensions, or annoying code behemoth

Cole Green 121 Jan 6, 2023
Icicle is a PHP library for writing asynchronous code using synchronous coding techniques

Icicle is now deprecated in favor of Amp v2.0. This version is is currently under development, but close to release. The v2.0 branches are amp_v2 in a 1.1k Dec 21, 2022
Asynchronous & Fault-tolerant PHP Framework for Distributed Applications.

Kraken PHP Framework ~ Release the Kraken! Note: This repository contains the core of the Kraken Framework. If you want to start developing new applic

Kraken 1.1k Dec 27, 2022
Asynchronous server-side framework for network applications implemented in PHP using libevent

phpDaemon Asynchronous framework in PHP. It has a huge number of features. Designed for highload. Each worker i

Vasily Zorin 1.5k Nov 30, 2022
🐺 Asynchronous Task Queue Based on Distributed Message Passing for PHP.

?? Asynchronous Task Queue Based on Distributed Message Passing for PHP.

Ahmed 36 Aug 11, 2022
🐼 Framework agnostic package using asynchronous HTTP requests and PHP generators to load paginated items of JSON APIs into Laravel lazy collections.

Framework agnostic package using asynchronous HTTP requests and generators to load paginated items of JSON APIs into Laravel lazy collections.

Andrea Marco Sartori 61 Dec 3, 2022
A minimalistic implementation of asynchronous SQL for PHP.

libSQL A minimalistic implementation of asynchronous SQL for PHP. Installation via DEVirion Install the DEVirion plugin and start your server. This wi

null 10 Dec 7, 2022
An asynchronous ClamAV wrapper written in PHP with amphp/socket

amphp-clamav An asynchronous ClamAV wrapper written with amphp/socket Installing composer require pato05/amphp-clamav Examples Ping and scan of a fil

Pato05 4 Feb 28, 2022
Artax is an asynchronous HTTP client for PHP based on Amp

Artax is an asynchronous HTTP client for PHP based on Amp. Its API simplifies standards-compliant HTTP resource traversal and RESTful web service consumption without obscuring the underlying protocol. The library manually implements HTTP over TCP sockets; as such it has no dependency on ext/curl.

AMPHP 21 Dec 14, 2022
Asynchronous WebSocket server

Ratchet A PHP library for asynchronously serving WebSockets. Build up your application through simple interfaces and re-use your application without c

Ratchet 5.9k Jan 6, 2023
Asynchronous WebSocket client

Pawl An asynchronous WebSocket client in PHP Install via composer: composer require ratchet/pawl Usage Pawl as a standalone app: Connect to an echo s

Ratchet 528 Dec 15, 2022
Asynchronous iterators and operators.

pipeline Asynchronous iterators and operators. Installation This package can be installed as a Composer dependency. composer require amphp/pipeline Ve

Amp 16 Nov 18, 2022