Amp\Http\Client\HttpClient 不能直接 new,因其依赖 Amp\Loop 事件循环驱动异步 I/O;未启动 Loop 时调用会静默失败,必须在 Loop::run() 回调中用 yield 解包 Promise 获取响应。

为什么 Amp\Http\Client\HttpClient 不能直接 new?
因为 Amp 的 HTTP 客户端不是传统类实例,它依赖事件循环(Amp\Loop)驱动异步 I/O。没启动 Loop 就调用请求,程序会立刻退出,什么也不返回——连错误都不报,只静默失败。
- 必须先调用
Amp\Loop::run()包裹整个异步逻辑,或在已运行的 Loop 中执行(比如 Swoole 或 ReactPHP 环境里) - 不能在同步脚本末尾简单写
new HttpClient()然后链式调用,那只是构造了一个未被调度的 Promise - 常见报错现象:
PHP Fatal error: Uncaught Amp\PromiseException: No active loop,但更常遇到的是“没输出、没报错、没响应”
request() 返回的是 Promise,不是 Response
你拿到的 $promise 是个待决议的异步占位符,不是真实响应。直接 var_dump($promise) 只能看到内部状态对象,不是 status code 或 body。
- 必须用
yield(协程)或Amp\Promise\wait()(阻塞等待)来取值;前者推荐,后者仅用于调试或 CLI 工具 - 在
Amp\Loop::run()回调里,只能用yield;如果强行用wait()会阻塞整个事件循环,毁掉并发意义 - 示例片段:
use Amp\Http\Client\HttpClient; use Amp\Http\Client\Request; Amp\Loop::run(function () { $client = new HttpClient(); $promise = $client->request(new Request('https://api.example.com/users')); // ✅ 正确:yield 解包 $response = yield $promise; echo $response->getStatus(); // 200 // ❌ 错误:这行不会执行,因为 $promise 不是响应 // echo $promise->getStatus(); });
并发发多个请求,别用 for + yield 串行等
写成 for ($i = 0; $i request(...); } 是伪并发——实际仍是逐个发起、逐个等待,总耗时 ≈ 所有请求时间之和。
- 正确做法:先批量生成所有
Promise,再用Amp\Promise\all()并发等待全部完成 -
all()返回一个 Promise,resolve 后得到数组,顺序与请求顺序一致,不丢序 - 注意内存:如果某 API 响应体很大(比如 10MB JSON),5 个同时加载可能吃光内存;可加
timeout或用first()控制上限 - 示例:
$promises = []; foreach ($urls as $url) { $promises[] = $client->request(new Request($url)); } $responses = yield Amp\Promise\all($promises); // 一次等全部
超时、重试、Header 设置都得在 Request 构造时传参
HttpClient 实例本身不管理超时或默认头;每个请求的配置是独立的,靠 Request 对象携带。漏设超时,某个挂起的 API 会让整个并发卡死。
立即学习“PHP免费学习笔记(深入)”;
- 超时必须显式设:
new Request($url, 'GET', [], ['timeout' => 5.0]),单位秒,支持小数 - Header 要用关联数组传入第三个参数:
['User-Agent' => 'my-app/1.0'],不是setHeader()链式调用 - 重试需手动实现:Amp 默认不重试;可在 Promise reject 后捕获
Amp\Http\Client\Exception\TimeoutException或ConnectException,再递归重发(建议限制次数) - 常见翻车点:把
timeout写成整数5,但 Amp 要求 float 类型,否则被忽略
sleep()、file_get_contents() 或 PDO 查询。这些操作一上,整个“异步”就退化成同步排队。



















