
本文详解 Guzzle 的异步请求机制,指出常见误区(如误以为 getAsync() 会自动触发执行),并提供基于 Promise 链、EachPromise 和并发控制的完整可运行方案。
本文详解 guzzle 的异步请求机制,指出常见误区(如误以为 `getasync()` 会自动触发执行),并提供基于 promise 链、`eachpromise` 和并发控制的完整可运行方案。
Guzzle 的异步请求本质是基于 PHP 的 Promise 模式(通过 GuzzleHttp\Promise 实现),它并非真正的“后台线程式”异步,而是非阻塞的 I/O 调度——所有异步操作必须显式触发执行(如调用 wait() 或 promise()->wait()),否则请求根本不会发出。你代码中 dd("NOT EXECUTED") 总是先执行,正是因为 $promises 生成器仅定义了 Promise 对象,但未被消费或等待;而 EachPromise 构造后若不调用 ->promise()->wait(),其内部的 Promise 也不会启动。
✅ 正确做法:确保 Promise 被消费并等待完成
以下是修复后的完整示例(已优化逻辑、增强健壮性):
use GuzzleHttp\Client;
use GuzzleHttp\Promise\EachPromise;
use GuzzleHttp\Exception\RequestException;
$page = $request->page;
$limit = $request->limit;
$pageStart = $page * $limit;
$startDate = $request->start_date;
$endDate = $request->end_date;
$query = $request->search ?? '';
$user_token = Crypt::decryptString($company->persistent_user_token);
$overallRecords = [];
$client = new Client([
'base_uri' => env('BADGEBOX_BASE_URL'),
'timeout' => 30, // 建议显式设置超时
]);
// Step 1: 创建 Promise 生成器(注意:yield 返回的是 Promise 对象)
$promises = function () use ($pageStart, $limit, $user_token, $client, $startDate, $endDate, $query) {
for ($page = $pageStart; $page getAsync($url)->then(
function ($response) use (&$overallRecords) {
$body = (string) $response->getBody();
$data = json_decode($body, true);
if (is_array($data)) {
$overallRecords = array_merge($overallRecords, $data);
}
return $response; // 可选:便于链式调试
},
function (RequestException $e) {
\Log::error('Async request failed', [
'url' => (string) $e->getRequest()->getUri(),
'message' => $e->getMessage()
]);
// 可选择抛出异常或静默忽略
}
);
}
};
// Step 2: 使用 EachPromise 并发执行(concurrency=10 表示最多 10 个并发)
$eachPromise = new EachPromise($promises(), [
'concurrency' => 10,
'fulfilled' => function ($value) {
// 单个请求成功时的回调(可选)
},
'rejected' => function ($reason) {
// 单个请求失败时的回调(已由 then 的第二个参数处理,此处可留空)
}
]);
// ✅ 关键:必须调用 wait() 才真正发起所有请求并阻塞直到全部完成
$eachPromise->promise()->wait();
// 此时 $overallRecords 已填充完毕
dd('ALL DONE', $overallRecords);
⚠️ 注意事项与最佳实践
-
不要依赖
dd()在then()中调试:dd()会终止脚本,导致后续 Promise 无法执行。改用Log::info()或断点调试。 -
URL 参数务必
urlencode():特别是query和user_token,避免特殊字符引发 400 错误。 -
始终设置
timeout:防止某个请求无限挂起,拖垮整个批处理。 -
错误处理不可省略:
getAsync()的then()第二个参数或EachPromise的rejected回调必须处理网络异常、JSON 解析失败等场景。 -
内存敏感场景慎用大并发:
concurrency=10适合多数 API,但若单页数据量极大,建议降至 3–5 并配合分页逻辑优化。 - 替代方案考虑:如需更高阶的异步能力(如协程),可结合 Swoole 或 Laravel Octane,但 Guzzle 本身仍需 Promise 驱动。
通过以上修正,你的异步请求将真正并发执行,"EXECUTED" 将按预期输出,且 dd("NOT EXECUTED") 会被替换为最终聚合的数据结果。










