03 - JS 异步与 Promise
目录
一、同步与异步
- JS 是单线程语言,只能同时做一件事
- 同步代码:按顺序逐行执行,阻塞后续代码
- 异步代码:不阻塞主线程,通过回调函数 / Promise 在适当时机执行
异步应用场景
- 网络请求:AJAX、Fetch、Axios 等
- 定时任务:
setTimeout、setInterval - 事件处理:用户点击、滚动等
- 文件操作:
FileReader异步读取文件 - 动画渲染:
requestAnimationFrame - 复杂计算:Web Worker 后台线程
- 数据库交互:IndexedDB
- 实时通信:WebSocket、SSE
- 并发控制:
Promise.all、async/await
二、Event Loop 事件循环
为何需要 Event Loop
JS 是单线程的,但需要处理异步操作(网络请求、定时器等)。事件循环通过任务队列和非阻塞 I/O 实现了并发执行。
核心组成
1. 调用栈(Call Stack)
- 存储同步任务的执行上下文
- 先进后出(LIFO)结构
2. Web APIs(浏览器环境)
- 处理异步操作(
setTimeout、fetch、DOM 事件) - 异步任务完成时,将回调函数推入任务队列
3. 任务队列
- 宏任务队列(Macrotask Queue):
setTimeout、setInterval、I/O、UI 渲染、DOM 事件回调 - 微任务队列(Microtask Queue):
Promise.then、MutationObserver、queueMicrotask()、process.nextTick(Node.js)
事件循环执行流程
- 执行同步代码(调用栈)
- 调用栈清空后,执行所有微任务
- (给浏览器一次 DOM 渲染的机会)
- 执行一个宏任务
- 重复 1-4
执行示例
js
console.log('1'); // 同步
setTimeout(() => console.log('2'), 0); // 宏任务
Promise.resolve().then(() => console.log('3')); // 微任务
console.log('4'); // 同步
// 输出:1 → 4 → 3 → 2js
console.log('1');
setTimeout(() => {
console.log('2');
Promise.resolve().then(() => console.log('3'));
}, 0);
setTimeout(() => console.log('4'), 0);
Promise.resolve().then(() => console.log('5'));
// 输出:1 → 5 → 2 → 3 → 4Call Stack 执行过程图示



执行 setTimeout 后,Web APIs 等待计时完成:

同步代码执行完后,Event Loop 检查 Callback Queue:

DOM 事件与 Event Loop

- JS 是单线程
- 异步(setTimeout、Ajax 等)使用回调,基于 Event Loop
- DOM 事件也使用回调(触发时机不同),同样基于 Event Loop
async/await 顺序问题

- 初始化 Promise 时,传入的回调函数立刻执行
- 同步代码执行完毕(Call Stack 清空)
- 执行微任务
- 尝试触发 DOM 渲染
- 触发 Event Loop,执行宏任务
三、宏任务与微任务
分类
- 宏任务:
setTimeout、setInterval、Ajax 网络请求的回调、DOM 事件回调(click、scroll 等) - 微任务:
Promise.then/catch、async/await(对前端来说)
微任务比宏任务执行更早。
js
console.log(100);
setTimeout(() => console.log(200)); // 宏任务
Promise.resolve().then(() => console.log(300)); // 微任务
console.log(400);
// 输出:100 → 400 → 300 → 200宏任务 vs 微任务与 DOM 渲染的关系
- 微任务:DOM 渲染前触发(ES 规范内,JS 引擎处理,无需浏览器帮助)
- 宏任务:DOM 渲染后触发(浏览器处理)


Event Loop 完整渲染流程
每一次 Call Stack 结束后:

JS Event Loop vs 浏览器 Event Loop
| 特性 | JS Event Loop | 浏览器 Event Loop |
|---|---|---|
| 主要任务 | 处理 JS 代码执行 | 处理 JS + 渲染 + 用户输入 |
| 任务队列 | 微任务 + 宏任务 | 还包括渲染队列、用户输入队列 |
| 渲染 | 不直接涉及 | 包括重排/重绘任务 |
四、Promise
三种状态
| 状态 | 含义 | 触发条件 |
|---|---|---|
pending | 初始状态,进行中 | 创建后的默认状态 |
fulfilled | 成功完成 | 执行 resolve(value) |
rejected | 失败 | 执行 reject(reason) |
状态变化不可逆:
pending → fulfilled或pending → rejected

状态与回调的关系
pending状态:不触发then或catchfulfilled状态:触发后续.then回调rejected状态:触发后续.catch回调
js
const p1 = Promise.resolve(100);
p1.then(data => console.log('data1=' + data))
.catch(err => console.error('err1=' + err));
const p2 = Promise.reject('错误');
p2.then(data => console.log('data2=' + data))
.catch(err => console.error('err2=' + err)); // 触发这里then 和 catch 对状态的影响
- 链式处理中,正常 → 触发后续 then
- 只要抛错 → 触发后续 catch
- 不管 then 还是 catch 处理完毕,只要不继续抛错,之后状态就是
fulfilled
Promise 面试题

打印:1 3

打印:1 2 3

打印:1 2
手写 Promise 加载图片
js
function loadImg(url) {
return new Promise((resolve, reject) => {
const img = document.createElement('img');
img.onload = () => resolve(img);
img.onerror = () => reject(new Error(`图片加载错误:${url}`));
img.src = url;
});
}
loadImg('https://example.com/img.jpg')
.then(img => {
console.log(`图片宽高:${img.width}, ${img.height}`);
return img;
})
.then(img => {
document.body.append(img);
})
.catch(err => console.error(err));五、async / await
目的
解决嵌套异步回调地狱(callback hell),使用同步写法彻底消灭回调函数。
基本用法
js
async function loadImg1() {
const img1 = await loadImg('https://example.com/img1.jpg');
return img1;
}
(async function() {
try {
const img1 = await loadImg1();
console.log(img1);
} catch (ex) {
console.error(ex);
}
})();与 Promise 的关系
async函数返回的都是 Promise 对象jsasync function fn1() { return 100; } console.log(fn1()); // Promise {<fulfilled>: 100} // 相当于 Promise.resolve(100)await相当于 Promise 的 thentry...catch代替 Promise 的 catch
await 的行为
js
// await Promise 对象:等待 resolve 后继续
(async function() {
const p = Promise.resolve(100);
const res = await p;
console.log(res); // 100
})();
// await 非 Promise:直接返回值
(async function() {
const res = await 100;
console.log(res); // 100
})();
// await rejected Promise:抛错,需 try...catch
(async function() {
const p = Promise.reject('some err');
try {
const res = await p;
} catch (ex) {
console.error(ex); // 'some err'
}
})();总结
async封装 Promiseawait处理 Promise 成功(相当于.then)try...catch处理 Promise 失败(相当于.catch)
异步本质
await 是同步写法,但本质仍是异步调用,基于 Event Loop:
js
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end'); // 相当于 .then 回调,是微任务
}
async function async2() {
console.log('async2');
}
console.log('script start');
async1();
console.log('script end');
/*
输出:
script start
async1 start
async2
script end
async1 end
*/forEach vs for...of 中的 await
js
// ❌ forEach:并发执行(同时触发所有异步,1秒后同时打印)
async function test1() {
[1, 2, 3].forEach(async x => {
const res = await multi(x);
console.log(res); // 1, 4, 9 同时打印
});
}
// ✅ for...of:串行执行(每隔 1 秒打印一个)
async function test2() {
for (let x of [1, 2, 3]) {
const res = await multi(x);
console.log(res); // 1, 4, 9 依次打印
}
}
答案:Promise(100), 100
六、for await...of
用于遍历异步可迭代对象(async iterable),逐个等待 Promise 解析。
js
function createPromise(val) {
return new Promise(resolve => setTimeout(() => resolve(val), 1000));
}
async function fetchData() {
const promises = [
createPromise('数据1'),
createPromise('数据2'),
createPromise('数据3')
];
for await (const data of promises) {
console.log(data); // 依次打印 "数据1", "数据2", "数据3"
}
}
fetchData();对比:Promise.all 一次性等待所有完成,返回结果数组:
js
Promise.all([p1, p2, p3]).then(res => console.log(res)); // [数据1, 数据2, 数据3]七、手写 Promise
js
class MyPromise {
state = 'pending';
value = undefined;
reason = undefined;
resolveCallbacks = [];
rejectCallbacks = [];
constructor(func) {
const resolveHandler = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.resolveCallbacks.forEach(fn => fn());
}
};
const rejectHandler = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
this.rejectCallbacks.forEach(fn => fn());
}
};
try {
func(resolveHandler, rejectHandler);
} catch (err) {
rejectHandler(err);
}
}
then(fn1, fn2) {
fn1 = typeof fn1 === 'function' ? fn1 : v => v;
fn2 = typeof fn2 === 'function' ? fn2 : e => { throw e; };
if (this.state === 'pending') {
return new MyPromise((resolve, reject) => {
this.resolveCallbacks.push(() => {
try { resolve(fn1(this.value)); } catch (err) { reject(err); }
});
this.rejectCallbacks.push(() => {
try { resolve(fn2(this.reason)); } catch (err) { reject(err); }
});
});
}
if (this.state === 'fulfilled') {
return new MyPromise((resolve, reject) => {
try { resolve(fn1(this.value)); } catch (err) { reject(err); }
});
}
if (this.state === 'rejected') {
return new MyPromise((resolve, reject) => {
try { resolve(fn2(this.reason)); } catch (err) { reject(err); }
});
}
}
catch(fn) {
return this.then(v => v, fn);
}
}
// 静态方法
MyPromise.resolve = value => new MyPromise(resolve => resolve(value));
MyPromise.reject = reason => new MyPromise((_, reject) => reject(reason));
MyPromise.all = function(promiseList = []) {
return new MyPromise((resolve, reject) => {
const resultList = [];
let count = 0;
promiseList.forEach(p => {
p.then(data => {
resultList.push(data);
if (++count >= promiseList.length) resolve(resultList);
}).catch(err => reject(err));
});
});
};
MyPromise.race = function(promiseList = []) {
return new MyPromise((resolve, reject) => {
let settled = false;
promiseList.forEach(p => {
p.then(data => {
if (!settled) { resolve(data); settled = true; }
}).catch(err => reject(err));
});
});
};