JavaScript异步编程与执行机制全面解析
理解事件循环:同步 → 微任务 → 宏任务的执行顺序掌握Promise状态:pending、fulfilled、rejected三种状态及转换理解async/await:本质是Promise的语法糖,await会暂停函数执行注意内存泄漏:避免创建永远pending的Promise希望本文能帮助你深入理解JavaScript的异步执行机制,在面试和实际开发中都能得心应手!你能解释为什么是微任务而se
·
JavaScript异步编程与执行机制全面解析
一、从一道经典面试题说起
先看这段代码,你能准确说出输出结果吗?
async function async1 () {
console.log('async1 start');
await new Promise(resolve => {
console.log('promise1')
})
console.log('async1 success');
return 'async1 end'
}
console.log('script start')
async1().then(res => console.log(res))
console.log('script end')
正确答案是:
script start
async1 start
promise1
script end
为什么后面的代码不执行?让我们深入分析。
二、JavaScript执行机制核心概念
2.1 单线程与事件循环
JavaScript是单线程语言,但通过事件循环(Event Loop) 实现了非阻塞异步操作。
console.log('1'); // 同步任务
setTimeout(() => {
console.log('2'); // 宏任务
}, 0);
Promise.resolve().then(() => {
console.log('3'); // 微任务
});
console.log('4'); // 同步任务
// 输出顺序:1 → 4 → 3 → 2
2.2 任务队列:微任务 vs 宏任务
| 任务类型 | 示例 | 执行时机 |
|---|---|---|
| 微任务 | Promise.then、process.nextTick、MutationObserver |
当前同步代码执行完后立即执行 |
| 宏任务 | setTimeout、setInterval、setImmediate、I/O操作 |
下一次事件循环开始时执行 |
三、Promise深度解析
3.1 Promise的三种状态
// 1. pending(进行中)
const p1 = new Promise(() => {});
console.log(p1); // Promise { <pending> }
// 2. fulfilled(已成功)
const p2 = Promise.resolve('success');
console.log(p2); // Promise { 'success' }
// 3. rejected(已失败)
const p3 = Promise.reject('error');
console.log(p3); // Promise { <rejected> 'error' }
3.2 Promise状态不可逆
const promise = new Promise((resolve, reject) => {
resolve('第一次resolve'); // 状态变为fulfilled
reject('错误'); // 被忽略
resolve('第二次resolve'); // 被忽略
});
promise.then(console.log); // 输出:第一次resolve
3.3 为什么面试题中的代码不执行?
await new Promise(resolve => {
console.log('promise1')
// 没有调用resolve/reject!
})
关键原因:
await会暂停async函数执行,等待后面的Promise解决- 如果Promise永远不resolve/reject,就永远保持pending状态
- 导致await后面的代码被"冻结",永远不会执行
四、async/await底层原理
4.1 async函数的本质
// async函数实际上是Promise的语法糖
async function example() {
return 'hello';
}
// 等价于
function example() {
return Promise.resolve('hello');
}
4.2 await的执行机制
async function test() {
const result = await somePromise;
console.log(result);
}
// 相当于
function test() {
return somePromise.then(result => {
console.log(result);
});
}
4.3 注意async函数本身不是异步任务,但它总是返回Promise,并且内部的await会引入异步行为。
async function example() {
console.log('同步代码'); // 这是同步执行的
await something; // 从这里开始产生异步
console.log('异步代码'); // 这是异步执行的
}
五、微任务与宏任务的执行顺序
5.1 完整的事件循环流程
- 执行同步代码
- 执行当前所有的微任务
- 执行一个宏任务
- 重复步骤2-3
console.log('1'); // 同步
setTimeout(() => {
console.log('2'); // 宏任务
Promise.resolve().then(() => {
console.log('3'); // 微任务(在宏任务内部)
});
}, 0);
Promise.resolve().then(() => {
console.log('4'); // 微任务
setTimeout(() => {
console.log('5'); // 宏任务(在微任务内部)
}, 0);
});
console.log('6'); // 同步
// 输出顺序:1 → 6 → 4 → 2 → 3 → 5
六、微任务穿插
微任务穿插特指:当Promise的.then()回调返回另一个Promise时,JavaScript引擎需要创建额外的微任务来解析这个返回的Promise,导致其他已经存在的微任务"插队"先执行的特殊现象。
情况1:返回普通值(正常链式调用)
Promise.resolve().then(() => {
console.log('1');
return '普通值';
}).then((res) => {
console.log(res); // '普通值'
});
Promise.resolve().then(() => {
console.log('3');
});
输出:1 → 3 → 普通值
这里只是正常的微任务队列调度,不是真正的穿插。
情况2:返回Promise(真正的微任务穿插)
Promise.resolve().then(() => {
console.log('1');
return Promise.resolve('2'); // 返回Promise!
}).then((res) => {
console.log(res); // '2'
});
Promise.resolve().then(() => {
console.log('3');
});
输出:1 → 3 → 2
这里发生了真正的微任务穿插。
为什么会产生穿插?底层机制:
返回普通值时:
return '普通值';
- 直接传递值给下一个then
- 只产生1个新的微任务(下一个then的回调)
返回Promise时:
return Promise.resolve('2');
- 需要2个额外的微任务:
- 微任务A:等待返回的Promise解析
- 微任务B:将解析后的值传递给下一个then
- 这2个微任务被追加到当前微任务队列末尾
执行过程对比:
无穿插(返回普通值):
微任务队列: [callback1, callback3]
执行callback1 → 输出1,产生微任务callback2
微任务队列: [callback3, callback2]
执行callback3 → 输出3
执行callback2 → 输出'普通值'
有穿插(返回Promise):
微任务队列: [callback1, callback3]
执行callback1 → 输出1,产生2个额外微任务[A, B]
微任务队列: [callback3, 微任务A, 微任务B]
执行callback3 → 输出3 ← 这就是"穿插"!
执行微任务A → 等待Promise解析
执行微任务B → 输出2
关键识别特征:
✅ 会发生微任务穿插:
return Promise.resolve()return new Promise()return asyncFunction()(async函数返回Promise)
❌ 不会发生微任务穿插:
return 普通值throw new Error()- 不return(相当于return undefined)
七、常见面试题分析
7.1 题目一:混合执行顺序
console.log('1');
setTimeout(() => {
console.log('2');
}, 0);
Promise.resolve().then(() => {
console.log('3');
});
console.log('4');
// 结果:1 → 4 → 3 → 2
7.2 题目二:嵌套Promise
Promise.resolve().then(() => {
console.log('1');
return Promise.resolve('2');
}).then((res) => {
console.log(res);
});
Promise.resolve().then(() => {
console.log('3');
});
// 结果:1 → 3 → 2
7.3 题目三:async/await与Promise混合!!
async function async1() {
console.log('1'); //这里是同步
await async2();//调用async2,产生微任务
console.log('2');//这里是异步 微任务!!
//await后面的代码相当是当promise请求成功以后的then函数
}
async function async2() {
console.log('3');
}
console.log('4');
setTimeout(() => {
console.log('5');
}, 0);
async1();
new Promise(resolve => {
console.log('6');
resolve();
}).then(() => {
console.log('7');
});
console.log('8');
// 结果:4 → 1 → 3 → 6 → 8 → 2 → 7 → 5
八、实战建议与最佳实践
8.1 避免Promise内存泄漏
// 错误做法:Promise永远pending
const promise = new Promise(() => {});
// 正确做法:添加超时机制
function withTimeout(promise, timeout) {
return Promise.race([
promise,
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), timeout)
)
]);
}
8.2 错误处理最佳实践
// 方式1:使用.catch()
asyncFunction()
.then(result => { /* 处理结果 */ })
.catch(error => { /* 处理错误 */ });
// 方式2:使用try/catch with async/await
async function main() {
try {
const result = await asyncFunction();
// 处理结果
} catch (error) {
// 处理错误
}
}
九、总结
JavaScript的异步编程机制看似复杂,但掌握核心原理后就能游刃有余:
- 理解事件循环:同步 → 微任务 → 宏任务的执行顺序
- 掌握Promise状态:pending、fulfilled、rejected三种状态及转换
- 理解async/await:本质是Promise的语法糖,await会暂停函数执行
- 注意内存泄漏:避免创建永远pending的Promise
希望本文能帮助你深入理解JavaScript的异步执行机制,在面试和实际开发中都能得心应手!
思考题: 你能解释为什么Promise.then是微任务而setTimeout是宏任务吗?欢迎在评论区讨论!
更多推荐


所有评论(0)