Power Query延迟递归异常:二次Endpoint轮询调用未执行求助
兄弟,我刚看完你的Power Query代码,完全懂你遇到的糟心事——明明写了递归逻辑要等结果就绪后重试,结果Fiddler只抓到一次第二个端点的请求,之后数据加载就卡着不动了对吧?这问题其实是Power Query的两个特性在“搞鬼”,咱们一步步拆解:
问题根源
1. 惰性求值导致递归未触发
你在else分支里写的Function.InvokeAfter(()=>@getResult(url),#duration(0,0,0,5)),本质上只是定义了一个「延迟执行的任务」,但Power Query是惰性求值的——它只会在真正需要结果的时候才去运行代码。这里你返回的是一个待执行的函数对象,而不是递归调用的结果,所以流程直接卡在这,不会继续触发重试。
2. Web.Contents默认缓存坑
就算你解决了惰性求值的问题,还有个隐藏坑:Web.Contents默认会缓存请求结果。如果第一次返回“未就绪”,第二次请求会直接用缓存的旧结果,Fiddler根本看不到新请求,等于重试白做了。
修复方案
针对这两个问题,咱们修改代码就行:
1. 让延迟后的递归立即执行
把else分支的代码改成Value.Invoke(Function.InvokeAfter(() => @getResult(url), #duration(0, 0, 0, 5)), {})——Value.Invoke会强制触发延迟后的函数执行,而不是返回函数本身,这样就能正常递归重试了。
2. 禁用Web.Contents缓存
在调用第二个端点的Web.Contents参数里加上CacheDuration = #duration(0, 0, 0, 0),确保每次请求都是全新的,不会用缓存的旧状态。
完整修复后的代码
let apikey = "MYAPIKEY", proxyendpoint = "URL OF THE FIRST ENDPOINT", bytesbody = File.Contents("FILE TO POST"), headers = [#"Ocp-Apim-Subscription-Key" = apikey], bytesresp = Web.Contents(proxyendpoint, [Headers=headers, Content=bytesbody]), jsonresp = Json.Document(bytesresp), opLoc = jsonresp[OperationLocation], getResult = (url) => let linesBody = Web.Contents(url, [ Headers=headers, CacheDuration = #duration(0, 0, 0, 0) // 禁用缓存,确保每次请求都是新的 ]), linesJson = Json.Document(linesBody), resultStatus = linesJson[status], linesData = if (resultStatus = "Succeeded") then linesJson[recognitionResult][lines] else Value.Invoke(Function.InvokeAfter(() => @getResult(url), #duration(0, 0, 0, 5)), {}) // 延迟后立即执行递归 in linesData, linesText = List.Transform(getResult(opLoc), each _[text]), table = Table.FromList(linesText) in table
进阶优化:加重试次数上限
为了避免无限递归(比如API一直返回未就绪),建议给递归加个重试次数限制,比如默认重试10次,到次数就抛出错误:
getResult = (url as text, optional retryCount as number) => let currentRetry = if retryCount = null then 10 else retryCount, // 默认重试10次 linesBody = Web.Contents(url, [Headers=headers, CacheDuration = #duration(0,0,0,0)]), linesJson = Json.Document(linesBody), resultStatus = linesJson[status], linesData = if resultStatus = "Succeeded" then linesJson[recognitionResult][lines] else if currentRetry > 0 then Value.Invoke(Function.InvokeAfter(() => @getResult(url, currentRetry - 1), #duration(0,0,0,5)), {}) else error "重试次数已达上限,结果仍未就绪" in linesData
调用的时候用getResult(opLoc)(用默认10次)或者getResult(opLoc, 15)(指定15次)就行。
内容的提问来源于stack exchange,提问作者Akos Nagy

