JavaScript Async函数返回Promise的机制与相等性问题
Async函数的核心规则是:无论函数内部return什么类型的值,调用后永远会返回一个全新的Promise实例,所有和返回值相关的行为都是围绕这个规则展开的。
为什么返回的Promise和内部return的原Promise引用不相等?
这是ECMAScript规范定义的标准行为,不是实现bug:
- Async函数执行到return语句时,不会直接把return后的值作为调用结果抛出,而是会走规范定义的异步返回值包装流程,生成一个全新的Promise对象,将其状态与return的值做绑定后返回。
- 哪怕你return的本身就是一个已经落定(fulfilled/rejected)的原生Promise实例,这个包装流程也不会跳过,因此新返回的Promise和原Promise必然是两个不同的引用,全等判断返回
false完全符合预期。
对应验证代码:
let promise = new Promise(resolve => resolve()); async function test() { return promise; } console.log(promise === test()); // 输出false
直接return Promise 和 await后return Promise的差异
两种写法最终都能拿到相同的落定值/错误原因,但存在3个可观测的实际差异:
微任务执行时机不同
直接return Promise时,async函数返回的新Promise会直接绑定到你return的原Promise上,状态同步只需要1层微任务调度;而使用return await promise写法时,await关键字会先暂停函数执行,等原Promise落定后才会恢复执行、触发return逻辑,会多产生1层微任务,执行时机更晚。
验证代码:async function directReturn() { return Promise.resolve(1); } async function awaitReturn() { return await Promise.resolve(1); } console.log('script start'); directReturn().then(() => console.log('direct return 触发')); awaitReturn().then(() => console.log('await return 触发')); Promise.resolve().then(() => console.log('外层空Promise 触发')); console.log('script end');实际输出顺序:
script start script end 外层空Promise 触发 direct return 触发 await return 触发错误栈信息不同
如果return的是rejected状态的Promise,return promise写法不会在当前async函数的执行上下文里抛出错误,最终的错误栈不会包含当前async函数的调用帧;而return await promise写法会在当前函数上下文等待Promise落定,一旦reject,错误栈会带上当前函数的信息,排查问题时更容易定位错误来源。代码执行时序不同
直接return Promise会立刻终止函数执行,return语句后的同步代码永远不会运行;而return await promise的写法中,await表达式之前的同步代码会正常执行,直到运行到await才会暂停函数。
示例:async function direct() { return Promise.reject('error'); console.log('这行永远不会执行'); } async function awaitRet() { const p = Promise.reject('error'); console.log('这行会正常打印'); // 位于await之前,属于同步执行逻辑 return await p; }
补充提示:日常开发中如果不需要利用上述差异,直接return Promise即可,少一层微任务调度性能略优;如果需要保留当前函数的错误栈,或者需要在await之后、return之前执行其他逻辑,再用
return await写法。
内容的提问来源于stack exchange,提问作者flasher1101

