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是同一个对象,抛出TypeErrorreturn 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,并传递解析值xresolve(x);}} catch (error) {if (called) return;called = true;// 如果获取then方法抛出异常,将Promise状态设置为rejected,并传递异常信息reject(error);}} else {// 如果解析值x是一个原始值,将Promise状态设置为fulfilled,并传递解析值xresolve(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」