示例说明
轮询的礼貌程度由间隔决定:五秒起步的固定间隔在任务要跑十分钟时产生一百多次无效请求。指数增长的间隔——五、十、二十、四十、六十封顶——让请求次数对数下降,API 压力与等待时长的平衡点自动逼近。硬上限是保险丝:任务卡死时脚本在有限时间内放弃并报错,而不是无限等下去。
技术要点
- 间隔指数增长并封顶防过长等待
- 状态分类驱动不同处置分支
- 尝试上限作为整体保险丝
代码解读
wait_for_completion 的每轮先请求再按状态分支:完成输出结果 URL 返回零、失败带错误信息返回一、进行中输出进度并进入退避;网络失败与未完成共用退避路径但消息不同。间隔翻倍逻辑在封顶判断后执行 sleep。MAX_ATTEMPTS 二十次配六十秒封顶,总时长可控。
使用建议
更优雅的方案是服务端 webhook 通知。Retry-After 头出现时应覆盖本地间隔。轮询期间的日志输出便于估算剩余等待。
代码示例
# throttle the polling rate to stay under the API limit
#!/usr/bin/env bash
set -euo pipefail
readonly URL="https://api.example.com/jobs/${JOB_ID:-job-1}"
readonly BASE_INTERVAL=5
readonly MAX_INTERVAL=60
readonly MAX_ATTEMPTS=20
wait_for_completion() {
local interval=${BASE_INTERVAL}
local attempt=0
while (( attempt < MAX_ATTEMPTS )); do
(( attempt++ )) || true
local status body
body=$(curl -sf --max-time 8 "${URL}") || {
echo "请求失败,${interval}s 后重试" >&2
sleep "${interval}"
continue
}
status=$(echo "${body}" | jq -r '.status')
case "${status}" in
completed)
echo "${body}" | jq -r '.resultUrl'
return 0
;;
failed)
echo "Error: 任务失败:$(echo "${body}" | jq -r '.error')" >&2
return 1
;;
pending|running)
echo "状态 ${status},${interval}s 后再查 (${attempt}/${MAX_ATTEMPTS})" >&2
;;
*)
echo "未知状态: ${status}" >&2
return 2
;;
esac
# 未完成时退避增长:对 API 更礼貌
interval=$(( interval * 2 ))
(( interval > MAX_INTERVAL )) && interval=${MAX_INTERVAL}
sleep "${interval}"
done
echo "Error: 轮询 ${MAX_ATTEMPTS} 次仍未完成" >&2
return 1
}
wait_for_completion