{"id":18926929,"url":"https://github.com/bedrockstreaming/tornado","last_synced_at":"2025-04-09T14:15:49.133Z","repository":{"id":43657181,"uuid":"145872131","full_name":"BedrockStreaming/Tornado","owner":"BedrockStreaming","description":"A library for asynchronous programming :tornado: 🐎","archived":false,"fork":false,"pushed_at":"2023-08-24T08:56:43.000Z","size":315,"stargazers_count":81,"open_issues_count":7,"forks_count":5,"subscribers_count":20,"default_branch":"master","last_synced_at":"2025-04-02T13:03:30.609Z","etag":null,"topics":["asynchronous","library","php"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/BedrockStreaming.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2018-08-23T15:23:26.000Z","updated_at":"2024-02-05T09:15:18.000Z","dependencies_parsed_at":"2024-01-19T11:32:28.023Z","dependency_job_id":null,"html_url":"https://github.com/BedrockStreaming/Tornado","commit_stats":{"total_commits":104,"total_committers":9,"mean_commits":"11.555555555555555","dds":0.25,"last_synced_commit":"a9546b791afadefd1a5f5d84c0eff165533080ea"},"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BedrockStreaming%2FTornado","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BedrockStreaming%2FTornado/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BedrockStreaming%2FTornado/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BedrockStreaming%2FTornado/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/BedrockStreaming","download_url":"https://codeload.github.com/BedrockStreaming/Tornado/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248054194,"owners_count":21039952,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["asynchronous","library","php"],"created_at":"2024-11-08T11:17:33.111Z","updated_at":"2025-04-09T14:15:49.109Z","avatar_url":"https://github.com/BedrockStreaming.png","language":"PHP","readme":"# Tornado 🌪🐎\n\u003cimg src=\"assets/Tornado-Logo.png?raw=true\" width=\"250\" align=\"right\" alt=\"Tornado Logo\"\u003e\n\n[![Build Status](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2FBedrockStreaming%2FTornado%2Fbadge%3Fref%3Dmaster\u0026style=flat)](https://actions-badge.atrox.dev/BedrockStreaming/Tornado/goto?ref=master)\n\nA library for asynchronous programming in [Php](https://secure.php.net/).\n\n*Tornado* is composed of several interfaces to write asynchronous programs using [generators](https://secure.php.net/manual/en/language.generators.php).\nThis library provides adapters for popular asynchronous frameworks ([ReactPhp](https://reactphp.org/), [Amp](https://amphp.org/))\nand built-in adapters to understand how to write your own.\n\n\n## Installation\n\nYou can install it using [Composer](https://getcomposer.org/):\n```bash\ncomposer require m6web/tornado\n```\n\nYou will also have to install additional dependencies related to the adapter you choose for your [`EventLoop`](src/EventLoop.php)\nyou may check our suggestions using [Composer](https://getcomposer.org/):\n```bash\ncomposer suggests --by-package\n```\n\nℹ️ *Tornado* includes its own [`EventLoop`](src/EventLoop.php) adapter to ease quick testing, and to show how you could write\n your own [`EventLoop`](src/EventLoop.php) optimized for your use case, but keep in mind that ⚠️***Tornado* adapters are not\n yet production ready**⚠️.\n\n\n\n## How to use it\nYou can find ready-to-use examples in [`examples`](https://github.com/M6Web/Tornado/tree/master/examples) directory,\nbut here some detailed explanations about asynchronous programing, and *Tornado* principles.\n\n### Dealing with promises\nThe [`EventLoop`](src/EventLoop.php) is the engine in charge of executing all asynchronous functions.\nIf one of those functions is *waiting* an asynchronous result (a [`Promise`](src/Promise.php))\nthe [`EventLoop`](src/EventLoop.php) is able to pause this function and to resume an other one ready to be executed.\n\nWhen you get a [`Promise`](src/Promise.php), the only way to retrieve its concrete value is to [`yield`](https://secure.php.net/manual/en/language.generators.syntax.php#control-structures.yield) it,\nletting the [`EventLoop`](src/EventLoop.php) deal internally with \n[Php Generators](https://secure.php.net/manual/en/language.generators.overview.php). \n```php\n/**\n * Sends a HTTP request a returns its body as a Json array.\n */\nfunction getJsonResponseAsync(Tornado\\HttpClient $httpClient, RequestInterface $request): \\Generator\n{\n    /** @var ResponseInterface $response */\n    $response = yield $httpClient-\u003esendRequest($request);\n\n    return json_decode((string) $response-\u003egetBody(), true);\n}\n```\n⚠️ Remember that the return type can **NOT** be `array` here,\neven if we expect that `json_decode` will return an `array`.\nSince we are creating a [`Generator`](https://secure.php.net/manual/en/language.generators.overview.php),\nthe return type is by definition `\\Generator`.\n \n### Asynchronous functions\nAs soon as your function needs to wait a [`Promise`](src/Promise.php), it becomes by definition an asynchronous function.\nTo execute it, you need to use [`EventLoop::async`](src/EventLoop.php) method.\nThe returned [`Promise`](src/Promise.php) will be resolved with the value returned by your function.\n```php\n/**\n * Returns a Promise that will be resolved with a Json array.\n */\nfunction requestJsonContent(Tornado\\EventLoop $eventLoop, Tornado\\HttpClient $httpClient): Tornado\\Promise\n{\n    $request = new Psr7\\Request(\n        'GET',\n        'http://httpbin.org/json',\n        ['accept' =\u003e 'application/json']\n    );\n\n    return $eventLoop-\u003easync(getJsonResponseAsync($httpClient, $request));\n}\n```\n⚠️ Keep in mind that's a bad practice to expose publicly a [`Generator`](https://secure.php.net/manual/en/language.generators.overview.php).\nYour asynchronous functions should return a [`Promise`](src/Promise.php)\nand keep its [`Generator`](https://secure.php.net/manual/en/language.generators.overview.php)\nas an implementation detail, you could choose to return a [`Promise`](src/Promise.php)\nin an other manner (see [dedicated examples](#resolving-your-own-promises)).\n\n### Running the event loop\nNow, you know that you have to create a generator to wait a [`Promise`](src/Promise.php),\nand then call [`EventLoop::async`](src/EventLoop.php) to execute the generator and obtain a new [`Promise`](src/Promise.php)…\nBut how can we wait the **first** [`Promise`](src/Promise.php)?\nActually, there is a second way to wait a [`Promise`](src/Promise.php), a **synchronous** one:\nthe [`EventLoop::wait`](src/EventLoop.php) method.\nIt means that **you should use it only once**, to wait synchronously the resolution of a predefined goal.\nInternally, this function will run a loop to handle all *events* until your goal is reached\n(or an error occurred, see [dedicated chapter](#error-management)).\n```php\nfunction waitResponseSynchronously(Tornado\\EventLoop $eventLoop, Tornado\\HttpClient $httpClient)\n{\n    /** @var array $jsonArray */\n    $jsonArray = $eventLoop-\u003ewait(requestJsonContent($eventLoop, $httpClient));\n    echo '\u003e\u003e\u003e '.json_encode($jsonArray).PHP_EOL;\n}\n```\nLike with the `yield` keyword,\nthe [`EventLoop::wait`](src/EventLoop.php) method will return the resolved value of the input [`Promise`](src/Promise.php),\nbut remember that you should use it only once during execution.\n\n### Concurrency\nTo reveal the true power of asynchronous programming, we have to introduce *concurrency* in our program.\nIf our goal is to send only one HTTP request and wait for it,\nthere is no gain to deal with an asynchronous request.\nHowever, as soon as you have at least two goals to reach,\nasynchronous functions will improve your performances thanks to concurrency.\nTo resolve several independent [`Promises`](src/Promise.php),\nuse [`EventLoop::promiseAll`](src/EventLoop.php) method to create a new [`Promise`](src/Promise.php)\nthat will be resolved when all others are resolved.\n```php\nfunction waitManyResponsesSynchronously(Tornado\\EventLoop $eventLoop, Tornado\\HttpClient $httpClient)\n{\n    $allJsonArrays = $eventLoop-\u003ewait(\n        $eventLoop-\u003epromiseAll(\n            requestJsonContent($eventLoop, $httpClient),\n            requestJsonContent($eventLoop, $httpClient),\n            requestJsonContent($eventLoop, $httpClient),\n            requestJsonContent($eventLoop, $httpClient)\n        )\n    );\n\n    foreach ($allJsonArrays as $index =\u003e $jsonArray) {\n        echo \"[$index]\u003e\u003e\u003e \".json_encode($jsonArray).PHP_EOL;\n    }\n}\n```\n\nIt's important to note that \nit will be more efficient to use [`EventLoop::promiseAll`](src/EventLoop.php)\ninstead of waiting each input [`Promise`](src/Promise.php) consecutively,\nbecause of concurrency.\nEach time that you have several promises to resolve,\nask yourself if you could wait them concurrently, especially when you deal with loops\n(take a look to [`EventLoop::promiseForeach`](src/EventLoop.php) function\nand [corresponding example](https://github.com/M6Web/Tornado/tree/master/examples/04-foreach.php)). \n\n### Resolving your own promises\nBy design, you cannot resolve a promise by yourself, you will need a [`Deferred`](src/Deferred.php).\nIt allows you to create a [`Promise`](src/Promise.php) and to resolve (or reject) it\nwhile not exposing these advanced controls.\n```php\nfunction promiseWaiter(Tornado\\Promise $promise): \\Generator\n{\n    echo \"I'm waiting a promise…\\n\";\n    $result = yield $promise;\n    echo \"I received [$result]!\\n\";\n}\n\nfunction deferredResolver(Tornado\\EventLoop $eventLoop, Tornado\\Deferred $deferred): \\Generator\n{\n    yield $eventLoop-\u003edelay(1000);\n    $deferred-\u003eresolve('Hello World!');\n}\n\nfunction waitDeferredSynchronously(Tornado\\EventLoop $eventLoop)\n{\n    $deferred = $eventLoop-\u003edeferred();\n    $eventLoop-\u003ewait($eventLoop-\u003epromiseAll(\n        $eventLoop-\u003easync(deferredResolver($eventLoop, $deferred)),\n        $eventLoop-\u003easync(promiseWaiter($deferred-\u003egetPromise()))\n    ));\n}\n```\n\n### Error management\nA [`Promise`](src/Promise.php) is *resolved* in case of success,\nbut it will be *rejected* with a [`Throwable`](https://secure.php.net/manual/fr/class.throwable.php)\nin case of error.\nWhile waiting a [`Promise`](src/Promise.php) with `yield` or [`EventLoop::wait`](src/EventLoop.php) an exception may be thrown,\nit's up to you to catch it or to let it propagate to the upper level.\nIf you throw an exception in an asynchronous function, this will reject the associated [`Promise`](src/Promise.php). \n```php\nfunction failingAsynchronousFunction(Tornado\\EventLoop $eventLoop): \\Generator\n{\n    yield $eventLoop-\u003eidle();\n\n    throw new \\Exception('This is an exception!');\n}\n\nfunction waitException(Tornado\\EventLoop $eventLoop)\n{\n    try {\n        $eventLoop-\u003ewait($eventLoop-\u003easync(failingAsynchronousFunction($eventLoop)));\n    } catch (\\Throwable $throwable) {\n        echo $throwable-\u003egetMessage().PHP_EOL;\n    }\n}\n```\n\nWhen using [`EventLoop::async`](src/EventLoop.php),\nall exceptions thrown inside the generator will reject the returned [`Promise`](src/Promise.php).\nIn case of a background computing you may ignore this [`Promise`](src/Promise.php) and not `yield` nor wait it,\nbut *Tornado* will still catch thrown exceptions to prevent to miss them.\nBy design, an ignored rejected [`Promise`](src/Promise.php) will throw its exception during its destruction.\nIt means that if you **really** want to ignore all exceptions (really?),\nyou have to catch and ignore them **explicitly** in your code.\n```php\n$ignoredPromise = $eventLoop-\u003easync((function() {\n  try {\n    yield from throwingGenerator();\n  } catch(\\Throwable $throwable) {\n      // I want to ignore all exceptions for this function\n  }\n})());\n```  \n\n## FAQ\n\n#### Is *Tornado* related to the [*Tornado* Python library](http://www.tornadoweb.org)?\nNo, even if these two libraries deal with asynchronous programming,\nthey are absolutely not related.\nThe name *Tornado* has been chosen in reference to the [horse ridden by Zorro](https://en.wikipedia.org/wiki/Tornado_%28horse%29).\n\n#### I :heart: your logo, who did it?\nThe *Tornado* logo has been designed by [Cécile Moret](https://cecilemoret.com/).\n\n## Contributing\n\nRunning unit tests:\n```bash\ncomposer tests-unit\n```\n\nRunning examples:\n```bash\ncomposer tests-examples\n```\n\nRunning PhpStan (static analysis):\n```bash\ncomposer static-analysis\n```\n\nCheck code style:\n```bash\ncomposer code-style-check\n```\n\nFix code style:\n```bash\ncomposer code-style-fix\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbedrockstreaming%2Ftornado","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbedrockstreaming%2Ftornado","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbedrockstreaming%2Ftornado/lists"}