1 Promise源码分析
Promise的基本工作原理
Promise构造函数
:Promise构造函数接受一个执行器函数作为参数,该函数有两个参数:resolve
和reject
。在构造函数内部,会创建一个Promise实例,并初始化其状态为pending。
状态转换
:当执行器函数调用resolve函数时,Promise的状态会变为fulfilled
;当调用reject函数时,状态会变为rejected
。同时,resolve和reject函数会触发回调函数的执行。
回调函数队列
:Promise内部维护一个回调函数队列,用于存储注册的回调函数。当Promise的状态已经变为fulfilled
或rejected
时,回调函数会立即执行;如果Promise的状态还是pending
,回调函数会被添加到回调函数队列中,等待状态变化时执行。
then()方法
:then()方法用于注册成功回调函数,它接受两个参数:onFulfilled
和onRejected
。当Promise的状态已经变为fulfilled
时,会执行onFulfilled
回调函数;当状态变为rejected
时,会执行onRejected
回调函数。then()方法返回一个新的Promise实例,可以实现链式调用。
catch()方法
:catch()方法用于注册失败回调函数,它只接受一个参数:onRejected
。它实际上是then()方法的一种特殊形式,相当于调用then(undefined, onRejected)
。
class Promise {
constructor(executor) {
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.onResolvedCallbacks.forEach((callback) => callback());
}
};
const reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach((callback) => callback());
}
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (value) => value;
onRejected = typeof onRejected === 'function' ? onRejected : (reason) => {
throw reason;
};
const promise2 = new Promise((resolve, reject) => {
// Promise的queueMicrotask方法是用于将一个微任务(microtask)添加到微任务队列中。
if (this.state === 'fulfilled') {
queueMicrotask(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
});
}
if (this.state === 'rejected') {
queueMicrotask(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
});
}
if (this.state === 'pending') {
this.onResolvedCallbacks.push(() => {
queueMicrotask(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
});
});
this.onRejectedCallbacks.push(() => {
queueMicrotask(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
});
});
}
});
return promise2;
}
catch(onRejected) {
return this.then(null, onRejected);
}
}
/**
* resolvePromise是Promise的内部方法之一,用于处理Promise的解析过程。
* 它接受三个参数:promise(要解析的Promise实例)、x(解析值)、resolve(用于解析Promise的回调函数)。
* resolvePromise的作用是根据解析值x的类型,决定如何处理Promise的解析过程。它遵循Promise/A+规范中的规则,根据不同的情况执行相应的操作。
*/
function resolvePromise(promise, x, resolve, reject) {
if (promise === x) {
// 如果promise和解析值x是同一个对象,抛出TypeError
return reject(new TypeError('Chaining cycle detected for promise'));
}
if (x instanceof Promise) {
// 如果解析值x是一个Promise实例,等待其状态变为fulfilled或rejected,然后继续解析
x.then(resolve, reject);
} else if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
let called = false;
try {
const then = x.then;
if (typeof then === 'function') {
then.call(
x,
(y) => {
if (called) return;
called = true;
resolvePromise(promise, y, resolve, reject);
},
(r) => {
if (called) return;
called = true;
reject(r);
}
);
} else {
// 如果解析值x是一个普通对象或函数,将Promise状态设置为fulfilled,并传递解析值x
resolve(x);
}
} catch (error) {
if (called) return;
called = true;
// 如果获取then方法抛出异常,将Promise状态设置为rejected,并传递异常信息
reject(error);
}
} else {
// 如果解析值x是一个原始值,将Promise状态设置为fulfilled,并传递解析值x
resolve(x);
}
}
2 题目背景
这一期还是@洛千陨 珍藏题,带着疑问去看Promise源码:为什么’6’比’hello’先打印?
const p1 = () => (new Promise((resolve, reject) => {
console.log(1);
resolve(6);
let p2 = new Promise((resolve, reject) => {
console.log(2);
const timeOut1 = setTimeout(() => {
console.log(3);
resolve(4);
}, 0)
resolve(5);
});
p2.then((arg) => {
console.log(arg);
return 'hello'
}).then((value) => {
console.log(value)
});
}));
const timeOut2 = setTimeout(() => {
console.log(8);
const p3 = new Promise(reject => {
reject(9);
}).then(res => {
console.log(res)
})
}, 0)
p1().then((arg) => {
console.log(arg);
});
console.log(10);
输出结果:
1 2 10 5 6 hello 8 9 3
输出结果分析
- 疑问:为什么’6’比’hello’先打印?
答: 参照源码,promise的then方法里面会返回一个promise。
①p2第一个then里的代码推入微任务队列(第一个),p1.then里的代码推入微任务队列(第二个);
②现在来执行第一个微任务打印'5'
,return 'hello’会把p2第二个then里的代码推入微任务队列(第三个);
③执行第二个微任务打印'6'
;
④执行第三个微任务打印'hello'
。 - 输出结果的具体分析可以看另一篇博客的题目(1):ECMAScript 6 - 通过输出题理解「Promise」