Node.js 8.10中await未等待异步函数Promise解析问题排查
首先,你遇到的TypeError: Cannot read property 'statusCode' of undefined错误,核心原因是最初的sendHttpRequest函数并没有正确返回一个包含response的Promise,哪怕它被标记为async。
1. async函数的本质:自动包装返回值,但不自动处理回调式异步
你说得没错,async函数确实会自动把函数内return的值包装成resolved状态的Promise,把throw的错误包装成rejected状态的Promise。但这里有个关键前提:这个逻辑只针对函数的同步返回,或者你明确返回的Promise。
看你最初的sendHttpRequest代码:
static async sendHttpRequest(options) { if (!options.timeout) options.timeout = 30000; request(options, (err, response) => { if( err ) { throw err; } else { return response; } }) }
这里的request是典型的回调式异步函数——它会立即执行并返回(通常返回一个请求对象,但你没接收它),而回调函数要等到HTTP请求完成才会执行。
但async函数并不会自动识别内部的回调异步逻辑,它会在request调用完成后(注意,是调用完成,不是请求完成)就直接返回一个resolved状态的Promise,值为undefined(因为函数没有任何return语句)。
所以当你在queryByName里写const response = await Requestor.sendHttpRequest(pageUrlOptions);时,await等待的是一个已经resolved的Promise,值是undefined,自然就会报错“无法读取undefined的statusCode”。
2. 修改后代码正常的原因:手动包装回调为Promise
你修改后的代码,手动用new Promise()把回调式的request包装了起来:
static async sendHttpRequest(options) { return new Promise((resolve, reject) => { if (!options.timeout) options.timeout = 30000; request(options, (err, response) => { if( err ) { reject(err); } else { resolve(response); } }) }); }
这时候,sendHttpRequest返回的是一个Promise,这个Promise会在HTTP请求成功时resolve(response),失败时reject(err)。await就能正确等待这个Promise的状态变化,拿到真正的response对象,所以代码恢复正常。
3. 关于根调用用回调的补充
另外,你提到根调用用了回调方式调用queryByName,这个其实不是导致当前错误的直接原因,但确实浪费了queryByName的async特性。queryByName是async函数,它本身会返回一个Promise,你完全可以用await或者.then()来处理结果,而不是用回调——不过这是代码风格的优化,和当前的错误无关。
总结一下:async函数帮你包装返回值,但不会帮你把回调式的异步逻辑自动转化为Promise,对于这类老的回调API,你必须手动用new Promise()做一层包装,或者使用request-promise这类已经Promise化的库来替代原生request。
内容的提问来源于stack exchange,提问作者Jakob Nielsen

