{"id":13545609,"url":"https://github.com/spatie/async","last_synced_at":"2025-05-15T01:00:38.945Z","repository":{"id":37250480,"uuid":"114228700","full_name":"spatie/async","owner":"spatie","description":"Easily run code asynchronously","archived":false,"fork":false,"pushed_at":"2025-04-22T07:31:17.000Z","size":298,"stargazers_count":2704,"open_issues_count":7,"forks_count":181,"subscribers_count":37,"default_branch":"main","last_synced_at":"2025-04-22T20:08:27.539Z","etag":null,"topics":["async","await","performance","php"],"latest_commit_sha":null,"homepage":"https://spatie.be/en/opensource/php","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/spatie.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":"spatie"}},"created_at":"2017-12-14T09:16:35.000Z","updated_at":"2025-04-22T15:29:44.000Z","dependencies_parsed_at":"2024-09-25T00:04:21.655Z","dependency_job_id":"260048a3-eaa3-498c-8b9e-e050f0097a24","html_url":"https://github.com/spatie/async","commit_stats":{"total_commits":220,"total_committers":34,"mean_commits":6.470588235294118,"dds":0.4363636363636364,"last_synced_commit":"777ca47c2cd26958e16d6a925e1d3fe19140b24a"},"previous_names":[],"tags_count":26,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spatie%2Fasync","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spatie%2Fasync/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spatie%2Fasync/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spatie%2Fasync/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/spatie","download_url":"https://codeload.github.com/spatie/async/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252882242,"owners_count":21819149,"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":["async","await","performance","php"],"created_at":"2024-08-01T11:01:06.937Z","updated_at":"2025-05-07T12:47:10.471Z","avatar_url":"https://github.com/spatie.png","language":"PHP","readme":"\u003cdiv align=\"left\"\u003e\n    \u003ca href=\"https://spatie.be/open-source?utm_source=github\u0026utm_medium=banner\u0026utm_campaign=async\"\u003e\n      \u003cpicture\u003e\n        \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://spatie.be/packages/header/async/html/dark.webp\"\u003e\n        \u003cimg alt=\"Logo for async\" src=\"https://spatie.be/packages/header/async/html/light.webp\" height=\"190\"\u003e\n      \u003c/picture\u003e\n    \u003c/a\u003e\n\n\u003ch1\u003eAsynchronous and parallel PHP\u003c/h1\u003e\n\n[![Latest Version on Packagist](https://img.shields.io/packagist/v/spatie/async.svg?style=flat-square)](https://packagist.org/packages/spatie/async)\n![Tests Status](https://img.shields.io/github/actions/workflow/status/spatie/async/run-tests.yml)\n[![Quality Score](https://img.shields.io/scrutinizer/g/spatie/async.svg?style=flat-square)](https://scrutinizer-ci.com/g/spatie/async)\n[![Total Downloads](https://img.shields.io/packagist/dt/spatie/async.svg?style=flat-square)](https://packagist.org/packages/spatie/async)\n    \n\u003c/div\u003e\n\nThis library provides a small and easy wrapper around PHP's PCNTL extension.\nIt allows running of different processes in parallel, with an easy-to-use API.\n\n## Support us\n\n[\u003cimg src=\"https://github-ads.s3.eu-central-1.amazonaws.com/async.jpg?t=1\" width=\"419px\" /\u003e](https://spatie.be/github-ad-click/async)\n\nWe invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us).\n\nWe highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards).\n\n## Installation\n\nYou can install the package via composer:\n\n```bash\ncomposer require spatie/async\n```\n\n## Usage\n\n```php\nuse Spatie\\Async\\Pool;\n\n$pool = Pool::create();\n\nforeach ($things as $thing) {\n    $pool-\u003eadd(function () use ($thing) {\n        // Do a thing\n    })-\u003ethen(function ($output) {\n        // Handle success\n    })-\u003ecatch(function (Throwable $exception) {\n        // Handle exception\n    });\n}\n\n$pool-\u003ewait();\n```\n\n### Event listeners\n\nWhen creating asynchronous processes, you'll get an instance of `ParallelProcess` returned.\nYou can add the following event hooks on a process.\n\n```php\n$pool\n    -\u003eadd(function () {\n        // ...\n    })\n    -\u003ethen(function ($output) {\n        // On success, `$output` is returned by the process or callable you passed to the queue.\n    })\n    -\u003ecatch(function ($exception) {\n        // When an exception is thrown from within a process, it's caught and passed here.\n    })\n    -\u003etimeout(function () {\n        // A process took too long to finish.\n    })\n;\n```\n\n### Functional API\n\nInstead of using methods on the `$pool` object, you may also use the `async` and `await` helper functions.\n\n```php\nuse Spatie\\Async\\Pool;\n\n$pool = Pool::create();\n\nforeach (range(1, 5) as $i) {\n    $pool[] = async(function () {\n        usleep(random_int(10, 1000));\n\n        return 2;\n    })-\u003ethen(function (int $output) {\n        $this-\u003ecounter += $output;\n    });\n}\n\nawait($pool);\n```\n\n### Error handling\n\nIf an `Exception` or `Error` is thrown from within a child process, it can be caught per process by specifying a callback in the `-\u003ecatch()` method.\n\n```php\n$pool\n    -\u003eadd(function () {\n        // ...\n    })\n    -\u003ecatch(function ($exception) {\n        // Handle the thrown exception for this child process.\n    })\n;\n```\n\nIf there's no error handler added, the error will be thrown in the parent process when calling `await()` or `$pool-\u003ewait()`.\n\nIf the child process would unexpectedly stop without throwing an `Throwable`, \nthe output written to `stderr` will be wrapped and thrown as `Spatie\\Async\\ParallelError` in the parent process.\n\n### Catching exceptions by type\n\nBy type hinting the `catch` functions, you can provide multiple error handlers, \neach for individual types of errors.\n\n```php\n$pool\n    -\u003eadd(function () {\n        throw new MyException('test');\n    })\n    -\u003ecatch(function (MyException $e) {\n        // Handle `MyException`\n    })\n    -\u003ecatch(function (OtherException $e) {\n        // Handle `OtherException`\n    });\n```\n\nNote that as soon as an exception is handled, it won't trigger any other handlers\n\n```php\n$pool\n    -\u003eadd(function () {\n        throw new MyException('test');\n    })\n    -\u003ecatch(function (MyException $e) {\n        // This one is triggerd when `MyException` is thrown\n    })\n    -\u003ecatch(function (Exception $e) {\n        // This one is not triggerd, even though `MyException` extends `Exception`\n    });\n```\n\n### Stopping a pool\n\nIf you need to stop a pool early, because the task it was performing has been completed by one\nof the child processes, you can use the `$pool-\u003estop()` method. This will prevent the \npool from starting any additional processes.\n\n```php\nuse Spatie\\Async\\Pool;\n\n$pool = Pool::create();\n\n// Generate 10k processes generating random numbers\nfor($i = 0; $i \u003c 10000; $i++) {\n    $pool-\u003eadd(function() use ($i) {\n        return rand(0, 100);\n    })-\u003ethen(function($output) use ($pool) {\n        // If one of them randomly picks 100, end the pool early.\n        if ($output === 100) {\n            $pool-\u003estop();\n        }\n    });\n}\n\n$pool-\u003ewait();\n```\n\nNote that a pool will be rendered useless after being stopped, and a new pool should be\ncreated if needed.\n\n### Using another PHP binary\n\nBy default the pool will use `php` to execute its child processes. You can configure another binary like so:\n\n```php\nPool::create()\n    -\u003ewithBinary('/path/to/php');\n```\n\n### Working with tasks\n\nBesides using closures, you can also work with a `Task`. \nA `Task` is useful in situations where you need more setup work in the child process.\nBecause a child process is always bootstrapped from nothing, chances are you'll want to initialise eg. the dependency container before executing the task.\nThe `Task` class makes this easier to do.\n\n```php\nuse Spatie\\Async\\Task;\n\nclass MyTask extends Task\n{\n    public function configure()\n    {\n        // Setup eg. dependency container, load config,...\n    }\n\n    public function run()\n    {\n        // Do the real work here.\n    }\n}\n\n// Add the task to the pool\n$pool-\u003eadd(new MyTask());\n```\n\n#### Simple tasks\n\nIf you want to encapsulate the logic of your task, but don't want to create a full blown `Task` object,\nyou may also pass an invokable object to the `Pool`.\n\n```php\nclass InvokableClass\n{\n    // ...\n\n    public function __invoke()\n    {\n        // ...\n    }\n}\n\n$pool-\u003eadd(new InvokableClass(/* ... */));\n```\n\n### Pool configuration\n\nYou're free to create as many pools as you want, each pool has its own queue of processes it will handle.\n\nA pool is configurable by the developer:\n\n```php\nuse Spatie\\Async\\Pool;\n\n$pool = Pool::create()\n\n// The maximum amount of processes which can run simultaneously.\n    -\u003econcurrency(20)\n\n// The maximum amount of time a process may take to finish in seconds\n// (decimal places are supported for more granular timeouts).\n    -\u003etimeout(15)\n\n// Configure which autoloader sub processes should use.\n    -\u003eautoload(__DIR__ . '/../../vendor/autoload.php')\n    \n// Configure how long the loop should sleep before re-checking the process statuses in microseconds.\n    -\u003esleepTime(50000)\n;\n```\n\n### Synchronous fallback\n\nIf the required extensions (`pcntl` and `posix`) are not installed in your current PHP runtime, the `Pool` will automatically fallback to synchronous execution of tasks.\n\nThe `Pool` class has a static method `isSupported` you can call to check whether your platform is able to run asynchronous processes. \n\nIf you're using a `Task` to run processes, only the `run` method of those tasks will be called when running in synchronous mode.\n\n## Behind the curtains\n\nWhen using this package, you're probably wondering what's happening underneath the surface.\n\nWe're using the `symfony/process` component to create and manage child processes in PHP.\nBy creating child processes on the fly, we're able to execute PHP scripts in parallel.\nThis parallelism can improve performance significantly when dealing with multiple synchronous tasks,\nwhich don't really need to wait for each other.\nBy giving these tasks a separate process to run on, the underlying operating system can take care of running them in parallel.\n\nThere's a caveat when dynamically spawning processes: you need to make sure that there won't be too many processes at once,\nor the application might crash.\nThe `Pool` class provided by this package takes care of handling as many processes as you want\nby scheduling and running them when it's possible.\n\nThat's the part that `async()` or `$pool-\u003eadd()` does. Now let's look at what `await()` or `$pool-\u003ewait()` does.\n\nWhen multiple processes are spawned, each can have a separate time to completion.\nOne process might eg. have to wait for a HTTP call, while the other has to process large amounts of data.\nSometimes you also have points in your code which have to wait until the result of a process is returned.\n\nThis is why we have to wait at a certain point in time: for all processes on a pool to finish,\nso we can be sure it's safe to continue without accidentally killing the child processes which aren't done yet.\n\nWaiting for all processes is done by using a `while` loop, which will wait until all processes are finished.\nDetermining when a process is finished is done by using a listener on the `SIGCHLD` signal.\nThis signal is emitted when a child process is finished by the OS kernel.\nAs of PHP 7.1, there's much better support for listening and handling signals,\nmaking this approach more performant than eg. using process forks or sockets for communication.\nYou can read more about it [here](https://wiki.php.net/rfc/async_signals).\n\nWhen a process is finished, its success event is triggered, which you can hook into with the `-\u003ethen()` function.\nLikewise, when a process fails or times out, the loop will update that process' status and move on.\nWhen all processes are finished, the while loop will see that there's nothing more to wait for, and stop.\nThis is the moment your parent process can continue to execute.\n\n### Comparison to other libraries\n\nWe've written a blog post containing more information about use cases for this package, as well as making comparisons to other asynchronous PHP libraries like ReactPHP and Amp: [http://stitcher.io/blog/asynchronous-php](http://stitcher.io/blog/asynchronous-php).\n\n## Testing\n\n``` bash\ncomposer test\n```\n\n## Changelog\n\nPlease see [CHANGELOG](CHANGELOG.md) for more information what has changed recently.\n\n## Contributing\n\nPlease see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details.\n\n### Security\n\nIf you've found a bug regarding security please mail [security@spatie.be](mailto:security@spatie.be) instead of using the issue tracker.\n\n## Postcardware\n\nYou're free to use this package, but if it makes it to your production environment we highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using.\n\nOur address is: Spatie, Kruikstraat 22, 2018 Antwerp, Belgium.\n\nWe publish all received postcards [on our company website](https://spatie.be/en/opensource/postcards).\n\n## Credits\n\n- [Brent Roose](https://github.com/brendt)\n- [All Contributors](../../contributors)\n\n## License\n\nThe MIT License (MIT). Please see [License File](LICENSE.md) for more information.\n","funding_links":["https://github.com/sponsors/spatie"],"categories":["PHP","异步网络通信框架( Asynchronous Event Driven Framework )"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspatie%2Fasync","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fspatie%2Fasync","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspatie%2Fasync/lists"}