异步JavaScript:如何同时运行两个函数并优化执行顺序?
问题详情
我正在加深对异步JavaScript的理解,编写了一个耗时的计数函数,处理不当会阻塞其他代码执行。测试时,第一个函数从1计数到10000000,第二个从10计数到100。
测试代码
async function LongTask(from, to) { //Count up let myNum = from console.log(`counting from ${myNum} to ${to}`) let start = new Date(); while (myNum != to) { //console.log(`myNum: ${myNum}`); await myNum++; } let end = new Date(); console.log(`counting from ${from} to ${to} done in ${end - start}ms!`); } //2 functions that take some time to finish LongTask(1, 10000000); LongTask(10, 100); //Do unrelated stuff console.log("Hello World!");
输出结果
counting from 1 to 10000000 counting from 10 to 100 Hello World! counting from 10 to 100 done in 2ms! counting from 1 to 10000000 done in 40389ms!
疑问
目前通过await myNum++实现了短任务(10到100)先完成,但第10行的await myNum++出现警告:'await' has no effect on the type of this expression。若移除await关键字,代码会阻塞,短任务需等待长任务执行完毕才能启动。是否误解了异步JavaScript的工作原理?有没有更优的方法确保短任务优先完成?
分析与解决方案
核心误解
你对await的用法确实存在偏差:await的作用是等待Promise对象的状态变更,而myNum++是同步数值运算,返回的是数字而非Promise。这里await能“生效”是因为JavaScript会自动将非Promise值包装为已resolve的Promise,每次await都会让当前任务暂停,把执行权交还给事件循环,给其他任务执行机会。但这种写法既不规范,还会引发大量不必要的事件循环切换,性能损耗严重。
正确的异步拆分方式
要避免阻塞事件循环,同时让短任务优先执行,需将长任务拆分为多个小任务片段,每执行一段就主动交出执行权,让事件循环调度其他任务。推荐用await Promise.resolve()实现这一点,代码示例:
async function LongTask(from, to) { let myNum = from; console.log(`counting from ${myNum} to ${to}`); let start = new Date(); while (myNum !== to) { myNum++; // 每执行10000次就交出执行权,平衡性能与响应性 if (myNum % 10000 === 0) { await Promise.resolve(); } } let end = new Date(); console.log(`counting from ${from} to ${to} done in ${end - start}ms!`); } LongTask(1, 10000000); LongTask(10, 100); console.log("Hello World!");
原理说明
JavaScript是单线程模型,所有同步代码会持续占用事件循环直至执行完毕。通过await Promise.resolve(),我们把长任务拆成多个小同步块,每个块执行完后,事件循环可以优先处理短任务的同步块(因为短任务总工作量小),从而实现短任务先完成的效果,同时不会阻塞其他代码执行。
进阶调度思路
如果需要更精细的优先级控制,可以维护一个优先级任务队列,利用微任务机制(如queueMicrotask)在事件循环空闲时优先执行高优先级任务片段。但针对当前场景,上述拆分方法已足够解决问题。
内容的提问来源于stack exchange,提问作者Curtis_L

