Composer本身不支持异步脚本执行,所谓“异步”实为通过run-script调用的PHP脚本内部用Guzzle Promises等实现并发,scripts字段仅顺序执行shell命令,无并发调度能力。

Composer 本身不提供原生异步脚本执行能力,所谓“Composer 异步脚本”,实际是通过 composer run-script 触发的自定义 PHP 脚本,再由脚本内部实现并发逻辑——别指望 composer 命令自己并行跑多个 scripts。
为什么 composer run-script 默认是同步阻塞的
当你在 composer.json 里定义:
"scripts": {
"fetch-data": "php scripts/fetch.php",
"send-report": "php scripts/report.php"
}
然后运行 composer run-script fetch-data send-report,它仍是串行执行:等 fetch-data 完全退出后,才启动 send-report。Composer 不解析脚本内容,也不 fork 进程,只是按顺序调用 shell 命令。
- 根本不存在 Composer 级别的“并发脚本调度器”
-
scripts字段本质是 shell 命令列表,不是任务队列 - 错误退出会中断后续脚本(除非显式加
&&或||控制)
真正可行的并发方案:在 PHP 脚本内用 Guzzle Promises
想并发请求多个 API、批量处理数据,必须把并发逻辑下沉到脚本自身。推荐用 guzzlehttp/promises,它轻量、符合 Promise/A+、与现代 HTTP 客户端天然兼容。
- 先安装:
composer require guzzlehttp/promises - 在
scripts/fetch.php中创建多个 Promise,用Promise\all()并发触发 - 避免用
curl_multi手动管理句柄——容易漏curl_close、超时难控、无统一错误链 - 注意:PHP CLI 默认单进程,Promise 并发 ≠ 多线程,而是非阻塞 I/O 轮询(依赖事件循环,Guzzle 内部用
select()或stream_select())
示例关键片段:
use GuzzleHttp\Promise;
use GuzzleHttp\Client;
$client = new Client();
$promises = [
'user' => $client->getAsync('https://api.example.com/user/123'),
'orders' => $client->getAsync('https://api.example.com/orders/123'),
'profile' => $client->getAsync('https://api.example.com/profile/123'),
];
$results = Promise\settle($promises)->wait(); // 等所有完成,返回状态数组
别踩这些坑:超时、错误传播、资源泄漏
并发脚本最容易在生产环境崩掉的地方,往往不是逻辑,而是边界控制。
-
settle()和all()行为不同:all()遇到任一 reject 就整体失败;settle()总是返回全部结果(含 fulfilled/rejected 状态),更适合容错场景 - 每个 Promise 必须设超时:
$client->getAsync($url, ['timeout' => 5]),否则一个慢接口拖垮整批请求 - CLI 模式下没有自动 GC,大量 Promise 未
wait()可能导致内存缓慢增长,尤其循环中反复 new Promise - 不要在 Promise 回调里抛异常——它不会冒泡到顶层,会被静默吞掉;改用
reject()显式传递错误
真正的并发控制点永远在 PHP 脚本里,Composer 只负责把你拉进那个脚本的入口。别试图用 composer.json 的 scripts 字段做任务编排,那不是它的设计目标。











