无await的JavaScript async函数执行机制、用途及与setTimeout(0)的差异
核心结论:无await的async函数,函数体是同步执行的
你自己的测试结果完全正确——当async函数里没有任何await语句时,它的函数体会和普通同步函数一样立刻执行,不会有任何延迟,也不会被放到事件循环的队列里。你看到的日志顺序Start heavy stuff → Finish heavy stuff → All done, synchronously就是最好的证明。
MDN那句话其实是针对包含await的async函数说的:当函数内部遇到await时,JavaScript会暂停async函数的执行,把后续代码放到微任务队列,等await绑定的Promise状态变更后,再从暂停的地方继续执行。但如果没有await,整个函数就不会触发任何异步调度逻辑,完全同步运行。
这种情况下声明async函数有实用价值吗?
分两种场景看:
有用的场景:如果你需要函数始终返回一个Promise,不管内部有没有异步操作。
async关键字会自动把函数的返回值(哪怕是普通值)包装成一个resolved状态的Promise,帮你省去手动写return Promise.resolve(xxx)的麻烦。比如一个函数可能根据参数决定是异步请求还是直接返回默认值,但你希望调用方可以统一用
.then()或者await处理,不用区分同步/异步:async function getContent(isRemote) { if (isRemote) { return await fetch('/api/content'); } // 自动包装成 Promise.resolve('本地默认内容') return '本地默认内容'; } // 调用方不用关心内部是同步还是异步,统一用await const content = await getContent(false);完全没用的场景:如果你的函数既没有异步逻辑,也不需要返回Promise,那加
async纯粹是画蛇添足——它不会带来任何好处,反而会多一层不必要的Promise包装,增加微小的性能开销。
和setTimeout(foo, 0)的区别?
这俩完全不是一回事:
直接调用
async foo():函数体同步执行,会阻塞当前的代码执行(就像你的测试里的大循环,会卡住直到循环结束)。setTimeout(foo, 0):会把foo的执行放到宏任务队列,等当前所有同步代码(包括你写的console.log('All done, synchronously'))全部执行完毕,才会从队列里取出foo执行。把你的代码改成下面这样,就能看到明显差异:
async function foo() { console.log('Start heavy stuff'); for (let i = 0; i < 90000000; ++i) { Math.random() } console.log('Finish heavy stuff') } setTimeout(foo, 0); console.log('All done, synchronously');日志顺序会变成:
All done, synchronously→Start heavy stuff→Finish heavy stuff,因为setTimeout的回调要等当前同步栈清空才会运行。
另外还有一个小细节:async函数即使没有await,返回的也是Promise,所以你可以给它加.then()回调,这个回调会在当前同步代码结束后,进入微任务队列执行:
async function foo() { console.log('同步执行的内容'); } foo().then(() => console.log('微任务队列里的回调')); console.log('当前同步代码结束');
日志顺序:同步执行的内容 → 当前同步代码结束 → 微任务队列里的回调,这是普通函数做不到的(普通函数返回非Promise值的话,.then()会报错)。
内容的提问来源于stack exchange,提问作者George

