1. 项目概述
"吃透JS异步编程:从Promise手写到底层协程,新手也能秒懂"这个标题直指现代JavaScript开发中最核心也最容易让人困惑的领域——异步编程。作为一名长期奋战在一线的前端开发者,我深知异步问题就像房间里的大象,每个JS开发者都见过它,但真正能说清楚它所有细节的人却不多。
这个内容的价值在于它构建了一个从表层API到底层原理的完整认知路径。Promise作为ES6引入的异步解决方案,已经成为现代JS开发的标配,但很多开发者仅仅停留在会用的层面。而协程(Coroutine)这个概念,更是隐藏在async/await语法糖背后的关键机制。理解这两者的关系,才能真正掌握JS异步编程的精髓。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 异步编程的核心挑战
2.1 为什么需要异步编程?
JavaScript作为一门单线程语言,其事件循环机制决定了它必须通过异步来处理耗时操作。想象一下你在快餐店点餐的场景:如果收银员必须等厨师做完汉堡才能服务下一位顾客(同步模式),队伍很快就会排到门外。而实际的情况是,收银员记下订单后就去服务下一位,厨师做好后会通知取餐(异步模式)——这正是JS处理IO操作的方式。
2.2 回调地狱与Promise的诞生
在Promise出现之前,开发者主要依靠回调函数处理异步:
javascript复制getUser(userId, function(user) {
getPosts(user.id, function(posts) {
getComments(posts[0].id, function(comments) {
// 更多嵌套...
});
});
});
这种"金字塔"式的代码不仅难以阅读,错误处理也极其复杂。Promise的出现就是为了解决这个问题,它通过链式调用将异步操作扁平化:
javascript复制getUser(userId)
.then(user => getPosts(user.id))
.then(posts => getComments(posts[0].id))
.catch(err => console.error(err));
3. 手写实现Promise核心
3.1 Promise的基本结构
一个最简单的Promise实现需要包含以下要素:
- 状态机(pending/fulfilled/rejected)
- 值存储(value/reason)
- 回调队列(onFulfilledCallbacks/onRejectedCallbacks)
javascript复制class MyPromise {
constructor(executor) {
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.onFulfilledCallbacks.forEach(fn => fn());
}
};
const reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn());
}
};
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
}
3.2 then方法的实现
then方法是Promise的核心,它需要处理三种情况:
- 当前Promise已完成:直接执行回调
- 当前Promise已拒绝:直接执行错误回调
- 当前Promise未完成:将回调存入队列
javascript复制then(onFulfilled, onRejected) {
const promise2 = new MyPromise((resolve, reject) => {
if (this.state === 'fulfilled') {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
} else if (this.state === 'rejected') {
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
} else {
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});
}
});
return promise2;
}
注意:这里使用setTimeout是为了确保回调总是异步执行,符合Promise/A+规范。resolvePromise函数用于处理thenable对象和递归解析。
4. 从Promise到协程
4.1 async/await的本质
async/await是ES2017引入的语法糖,它让异步代码看起来像同步代码。实际上:
- async函数总是返回一个Promise
- await后面可以跟任何thenable对象
- 本质上是通过生成器(Generator)+ Promise实现的协程
javascript复制async function fetchData() {
const user = await getUser();
const posts = await getPosts(user.id);
return posts;
}
// 等价于
function fetchData() {
return spawn(function*() {
const user = yield getUser();
const posts = yield getPosts(user.id);
return posts;
});
}
4.2 协程的实现原理
协程是一种比线程更轻量级的并发模型,它允许函数在执行过程中暂停和恢复。JavaScript通过生成器函数实现协程:
javascript复制function spa
