ES6 Promise源码解析 (从Promise功能的角度看Promise源码实现)
首先,我们再来回顾一下Promise的基本用法,看代码
new Promise((resolve, reject) => {
try {
if (success) {
resolve()
}
if (fail) {
reject()
}
} catch (err) {
reject(err)
}
}).then((res) => {
console.log(res)
}, (err) => {
console.log(err)
}).then((res) => {
console.log(res)
}, (err) => {
console.log(err)
}).catch((err => {
console.log(err)
}))
我们先从上面的用法上,总结出一些Promise的特点
- 首先,毫无疑问,Promise()是一个构造函数, 并且,存在3种状态,pending, fulfilled(也可以叫Resolved), rejected。分别表示等待时,成功时,失败时
- Promise实例化时,传了个参数,并且这个参数是个函数(并且是个立即执行函数),同时这个函数还有两个参数,且这两个参数,依然是函数,分别是resolve(), reject()
- then()函数中的第一个参数(后文我们统称为then的resolve回调),是在调用then()方法的Promise对象的状态变为fulfilled时被执行的,而第二个参数(后文我们统称为rejected回调),是在Promise对象的状态变成rejected时被调用的。
- 通过第四代,我们还可以看出,在resolve()函数执行时,是将Promise对象的状态变更成了fulfilled,从而触发了then的resolve回调函数的执行。而reject()函数执行时,是将Promise对象的状态变更成了rejected,从而触发了then的reject回调的执行
- resolve(res)时的值,就是then的resolve回调的参数,reject(err)的值就是thenreject回调的参数
- then()函数和catch()函数可以被链式调用
Promise基础雏形
ES6 Promise源码解析 (从Promise功能的角度看Promise源码实现)_查看promise源码-CSDN博客
知晓了他是个构造函数,那我们就创建个构造函数,并定义好状态,并立即执行实例化时传入的函数
class MyPromise {
constructor (fun) {
// 定义初始状态(3个状态分别是pending, fulfilled, rejected)
this.status = 'pending'
// 定义两个变量分别来存储成功时值和失败时的值
this.resolveValue = null
this.rejectValue = null
// 定义resolve函数
let resolve = () => {}
// 定义reject函数
let reject = () => {}
try {
fun(resolve, reject)
} catch (err) {
reject(err)
}
}
}
此时,构造函数最基本的样子已经有了,定义了一个状态,执行了立即执行函数。并将resolve, reject传入到立即执行函数中。下面我们来完善下 resolve, reject
let resolve = (val) => {
// 1、将状态变更为fulfilled, 但是注意一点,Promise是有个特点的,就是状态只能由pending状态变更为fulfilled或者由pending状态变更为rejected。且,状态变化后,不会再变化。故,我们需要先判断当前是否是等待状态pending
if (this.status === 'pending') { // this指向实例化出来Promise对象
this.status = 'fulfilled'
// 2、保存resolve时的值,以便后面调用then()方法时使用
this.resolveValue = val
}
}
let reject = (val) => {
// 1、状态变更为rejected
if (this.status === 'pending') {
this.status = 'rejected'
// 2、保存reject()时的值
this.rejectValue = val
}
}
此时,resolve, reject已经有了,new Promise()时,已经可以调用resolve()方法和reject()方法了。并且,resolve()和reject()时,promise状态也已经发生了改变。并保存了resolve和reject出来的值。
在下一步,状态已经发生改变了,我们是不是要触发then的resolve回调,或者reject回调了。所以,我们来实现then()函数
MyPromise.prototype.then = (onFullFilled, onRejected) => {
// onFullFilled, onRejected分别resolve()时的回调函数和reject()时的回调函数
// 此时,判断状态,不同状态时,分别执行不同的回调
if (this.status === 'fulfilled') {
onFullFilled(this.resolveValue)
}
if (this.status === 'rejected') {
onRejected(this.rejectValue )
}
// 上面的this指向的是调用then的promise实例,故可以直接拿到状态和返回值
}
此时,我们一条执行流程应该是走完了,下面,我们来测试下
class MyPromise {
constructor (fun) {
// 定义初始状态(3个状态分别是pending, fulfilled, rejected)
this.status = 'pending'
// 定义两个变量分别来存储成功时值和失败时的值
this.resolveValue = null
this.rejectValue = null
// 定义resolve函数
let resolve = (val) => {
// 1、将状态变更为fulfilled, 但是注意一点,Promise是有个特点的,就是状态只能由pending状态变更为fulfilled或者由pending状态变更为rejected。且,状态变化后,不会再变化。故,我们需要先判断当前是否是等待状态pending
if (this.status === 'pending') { // this指向实例化出来Promise对象
this.status = 'fulfilled'
// 2、保存resolve时的值,以便后面调用then()方法时使用
this.resolveValue = val
}
}
// 定义reject函数
let reject = (val) => {
// 1、状态变更为rejected
if (this.status === 'pending') {
this.status = 'rejected'
// 2、保存reject()时的值
this.rejectValue = val
}
}
try {
fun(resolve, reject)
} catch (err) {
reject(err)
}
}
}
MyPromise.prototype.then = function (onFullFilled, onRejected) {
// onFulfilled, onRejected分别resolve()时的回调函数和reject()时的回调函数
// 此时,判断状态,不同状态时,分别执行不同的回调
if (this.status === 'fulfilled') {
onFullFilled(this.resolveValue)
}
if (this.status === 'rejected') {
onRejected(this.rejectValue )
}
// 上面的this指向的是调用then的promise实例,故可以直接拿到状态和返回值
}
const promise1 = new MyPromise((resolve, reject) => {
resolve(123)
})
promise1.then((res) => {
console.log(res) // 123
}, (err) => {
console.log(err)
})
输出了123
then
到此时,只是完成了基本,但仍存在很多问题。大家发现没有,我们在then函数中,只判断了状态为fufilled时,调了onFullFilled,状态为rejected时,调了onRejected。但如果then()函数被调用时,promise的状态还并未发生改变(也就是还处于pending时),那then()函数内的代码是不是不会执行拉。因为我们并没有写pending状态时的处理代码。如以下情况
const promise1 = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(123)
}, 0)
})
promise1.then((res) => {
console.log(res)
}, (err) => {
console.log(err)
})
没有输出
上面的代码中,立即执行函数内是一个异步代码,此时,是不是先执行了promise1.then(),然后才执行setTimeout中的回调函数啊。所以,此时,当promise1.then()执行时,状态已经是pending,故不会有任何输出。这就有问题了。而我们所希望的,是不是在resolve()或者reject()执行的时候,去触发then()函数的resolve回调或者rejected回调执行啊。 那该怎么做。我先说下思路。在then函数执行时,如果状态还是pending的话,我们是不是可以先把then()的两个回调函数先给他保存起来。然后在resolve()或者reject()的时候,再去触发这个函数的调用呢。我们来写一下 1、先定义两个变量用来保存then()的回调函数
this.onFullFilledList = []
this.onRejectedList = []
2、then()执行时,如果状态还未发生改变(还是pending时),那么就将回调函数先保存起来
MyPromise.prototype.then = function (onFullFilled, onRejected) {
// onFulfilled, onRejected分别resolve()时的回调函数和reject()时的回调函数
// 此时,判断状态,不同状态时,分别执行不同的回调
if (this.status === 'fulfilled') {
onFullFilled(this.resolveValue)
}
if (this.status === 'rejected') {
onRejected(this.rejectValue )
}
if (this.status === 'pending') {
// 保存的是一个函数,而函数内是回调的执行代码,当我们执行被保存的函数时,函数内的onFullFilled和onRejected是不是也就跟着执行拉
this.onFullFilledList.push(() => {
onFullFilled(this.resolveValue)
})
this.onRejectedList.push(() => {
onRejected(this.rejectValue )
})
}
// 上面的this指向的是调用then的promise实例,故可以直接拿到状态和返回值
}
3、在resolve()和reject()的时候, 去取onFullFilledList,onRejectedList两个队列中的函数,并依次执行
// 定义resolve函数
let resolve = (val) => {
// 1、将状态变更为fulfilled, 但是注意一点,Promise是有个特点的,就是状态只能由pending状态变更为fulfilled或者由pending状态变更为rejected。且,状态变化后,不会再变化。故,我们需要先判断当前是否是等待状态pending
if (this.status === 'pending') { // this指向实例化出来Promise对象
this.status = 'fulfilled'
// 2、保存resolve时的值,以便后面调用then()方法时使用
this.resolveValue = val
// 执行then的resolve回调
this.onFullFilledList.forEach(funItem => funItem())
}
}
// 定义reject函数
let reject = (val) => {
// 1、状态变更为rejected
if (this.status === 'pending') {
this.status = 'rejected'
// 2、保存reject()时的值
this.rejectValue = val
// 执行then的reject回调
this.onRejectedList.forEach(funItem => funItem())
}
}
到此,就不会再有因为异步代码而执行不了的问题了。看下完整代码,并验证下
class MyPromise {
constructor(fun) {
// 定义初始状态(3个状态分别是pending, fulfilled, rejected)
this.status = 'pending'
// 定义两个变量分别来存储成功时值和失败时的值
this.resolveValue = null
this.rejectValue = null
this.onFullFilledList = []
this.onRejectedList = []
// 定义resolve函数
let resolve = (val) => {
// 1、将状态变更为fulfilled, 但是注意一点,Promise是有个特点的,就是状态只能由pending状态变更为fulfilled或者由pending状态变更为rejected。且,状态变化后,不会再变化。故,我们需要先判断当前是否是等待状态pending
if (this.status === 'pending') { // this指向实例化出来Promise对象
this.status = 'fulfilled'
// 2、保存resolve时的值,以便后面调用then()方法时使用
this.resolveValue = val
// 执行then的resolve回调
this.onFullFilledList.forEach(funItem => funItem())
}
}
// 定义reject函数
let reject = (val) => {
// 1、状态变更为rejected
if (this.status === 'pending') {
this.status = 'rejected'
// 2、保存reject()时的值
this.rejectValue = val
// 执行then的reject回调
this.onRejectedList.forEach(funItem => funItem())
}
}
try {
fun(resolve, reject)
} catch (err) {
reject(err)
}
}
}
MyPromise.prototype.then = function (onFullFilled, onRejected) {
// onFulfilled, onRejected分别resolve()时的回调函数和reject()时的回调函数
// 此时,判断状态,不同状态时,分别执行不同的回调
if (this.status === 'fulfilled') {
onFullFilled(this.resolveValue)
}
if (this.status === 'rejected') {
onRejected(this.rejectValue )
}
if (this.status === 'pending') {
this.onFullFilledList.push(() => {
onFullFilled(this.resolveValue)
})
this.onRejectedList.push(() => {
onRejected(this.rejectValue )
})
}
// 上面的this指向的是调用then的promise实例,故可以直接拿到状态和返回值
}
const promise1 = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(123)
}, 0)
})
promise1.then((res) => {
console.log(res) // 123
}, (err) => {
console.log(err)
})
输出了123
微任务队列
执行优先级 Promise 的回调(.then/catch/finally)属于微任务,优先级高于宏任务(如 setTimeout):
setTimeout(() => console.log('macro'), 0);
Promise.resolve().then(() => console.log('micro'));
// 输出顺序:micro → macro
事件循环机制 微任务会在当前宏任务执行完成后立即执行,确保 Promise 的高响应性。
Promise 方法对比表
| 方法名 | 类型 | 功能描述 | 使用场景 | 示例 |
|---|---|---|---|---|
.then() | 实例方法 | 添加成功或失败的回调函数,返回新 Promise,支持链式调用 | 链式异步操作 | fetch().then(res => res.json()) |
.catch() | 实例方法 | 捕获链式调用中的错误(等效于 .then(null, onRejected)) | 全局错误处理 | fetch().catch(err => console.error(err)) |
.finally() | 实例方法 | 无论成功/失败,最终执行的回调(无返回值) | 清理资源(如隐藏加载动画) | fetch().finally(() => hideSpinner()) |
Promise.all() | 静态方法 | 所有 Promise 成功时返回结果数组;任意失败立即拒绝 | 并行执行多个独立任务(需全部成功) | Promise.all([fetchA(), fetchB()]).then([a, b] => merge(a, b)) |
Promise.race() | 静态方法 | 返回第一个 resolved(成功/失败)的 Promise 结果 | 超时控制或竞速请求 | Promise.race([fetch(), timeout(5000)]).then(first => ...) |
Promise.allSettled() | 静态方法 | 等待所有 Promise 完成,返回包含状态和结果的对象数组 | 记录所有操作的最终结果(无论成功失败) | Promise.allSettled([task1(), task2()]).then(results => log(results)) |
Promise.any() | 静态方法 | 返回第一个成功的 Promise;全部失败则聚合错误 | 多备用源请求(取首个可用结果) | Promise.any([api1(), api2()]).then(data => ...) |
Promise.resolve() | 静态方法 | 快速创建已解决的 Promise(若参数是 Promise,则直接返回) | 将同步值包装为 Promise | Promise.resolve(42).then(v => ...) |
Promise.reject() | 静态方法 | 快速创建已拒绝的 Promise | 快速抛出错误 | Promise.reject(new Error('Fail')).catch(err => ...) |
关键特性对比
| 方法 | 成功条件 | 失败条件 | 返回值结构 |
|---|---|---|---|
Promise.all() | 全部成功 | 任意一个失败 | 结果数组(按输入顺序) |
Promise.race() | 第一个 resolved(成功/失败) | 第一个 rejected | 第一个 resolved 的结果 |
Promise.allSettled() | 不关心成功/失败,全部完成 | 无 | 对象数组(含 status 和 value) |
Promise.any() | 任意一个成功 | 全部失败 | 第一个成功结果(或 AggregateError) |
使用场景速查
- 并行任务,需全部成功 →
Promise.all() - 竞速或超时控制 →
Promise.race() - 记录所有任务最终状态 →
Promise.allSettled() - 多备用请求,取首个成功 →
Promise.any() - 链式操作与错误统一处理 →
.then()+.catch()
如果需要更详细的参数说明或兼容性信息,可参考 MDN Promise 文档。
async awaie 会阻塞代码执行吗
- 首先,回顾async/await的基本概念。async函数返回一个Promise,而await用于等待Promise的解决。用户可能认为await会像同步代码那样阻塞整个线程,但实际上JavaScript是单线程的,async/await并不会阻塞事件循环。
- 接下来,需要明确“阻塞”在JavaScript中的含义。同步代码会阻塞主线程,直到操作完成,比如循环或长时间计算。而异步操作(如setTimeout、Promise)会将任务放入任务队列,主线程继续执行后续代码。async/await本质上是基于Promise的语法糖,所以其行为应与Promise类似,即非阻塞。
- 然后,考虑用户可能的误区。用户可能在看到await时,误以为后面的代码会等待,导致整个程序停止。需要解释虽然await会暂停async函数的执行,但不会阻塞主线程,其他任务(如事件处理、定时器)仍可继续执行。
- 举例说明,比如在async函数中使用await调用一个网络请求,函数内会暂停,直到请求完成,但函数外的代码可以继续执行。同时,需要提到事件循环的机制,如何将异步任务处理到微任务队列中,从而不影响主线程。
- 还要对比同步阻塞和异步非阻塞的区别,帮助用户理解为什么async/await不会导致阻塞。可能用代码示例来展示两者的不同行为,比如同步等待与await的区别。
- 另外,需要指出常见的误解,比如在async函数中有多个await时,每个await都会按顺序执行,但整个函数仍是异步的,不会阻塞其他代码。比如在一个async函数中,两次await调用接口,第二个会等第一个完成,但页面UI仍可响应。
- 最后,总结关键点:async/await不会阻塞主线程,只是暂停函数内部的执行,通过事件循环机制让出主线程,保持应用响应。同时提醒用户避免在async函数中执行长时间同步代码,这样才会真正阻塞。
async/await 不会阻塞 JavaScript 的主线程代码执行,但它会暂停当前 async 函数的内部执行,直到 await 的 Promise 完成。这是异步非阻塞的关键特性。
核心原理
-
主线程不受阻 当遇到
await时:- 暂停
async函数内后续代码的执行(函数内部“看似”阻塞)。 - 释放主线程,让事件循环继续处理其他任务(如 UI 渲染、定时器、网络请求等)。
- 暂停
-
基于事件循环的机制
await后的代码会被包装成微任务,在 Promise 完成后加入微任务队列。- 主线程空闲时,事件循环会优先执行微任务,恢复
async函数的执行。
代码示例
async function fetchData() {
console.log("开始请求数据");
const data = await fetch('https://api.example.com'); // 暂停函数内部,释放主线程
console.log("数据获取完成:", data); // 微任务中恢复执行
}
console.log("脚本启动");
fetchData();
console.log("脚本继续执行"); // 主线程立即执行,无需等待 fetchData 完成
输出顺序:
脚本启动
开始请求数据
脚本继续执行
(网络请求完成后)数据获取完成: ...
执行流程示意图
async function demo() {
console.log(1);
await console.log(2); // 2 是同步代码,立即执行,但 await 会等待其返回值(undefined)被解析
console.log(3); // 此处代码会被包装成微任务,主线程空闲后执行
}
console.log('start');
demo();
console.log('end');
// 输出顺序:
// 'start' → 1 → 2 → 'end' → 3
输出顺序的问题是由于JavaScript的异步执行机制导致的。
让我解释一下代码执行过程:
首先执行 console.log('start')
然后调用 demo() 函数:
函数内先执行 console.log(1)
遇到 await console.log(2),这里有个问题:console.log(2) 不是一个返回Promise的函数,但JavaScript会自动将它包装成一个已解决的Promise
执行 console.log(2) 后,由于await,函数暂停执行,控制权返回到主线程
主线程继续执行,打印 console.log('end')
当主线程任务执行完毕后,事件循环会回到之前暂停的异步操作,继续执行 demo() 函数中的 console.log(3)
所以输出顺序是: start → 1 → 2 → end → 3
这是因为在异步函数中使用 await 后的代码会被放入微任务队列(microtask queue),只有当主线程执行完毕后才会执行这些微任务。