一、介绍
实现一个简化的 Promise,包括基本的 then、catch、resolve、reject。
二、具体实现
class MyPromise {
constructor(executor) {
console.log('MyPromise constructor...')
this.state = 'pending'; // pending, fulfilled, rejected
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(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);
}
}
then(onFulfilled, onRejected) {
console.log('then...');
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };
const promise2 = new MyPromise((resolve, reject) => {
console.log('this.state: ', this.state);
if (this.state === 'fulfilled') {
setTimeout(() => {
try {
const x = onFulfilled(this.value); // this.value 为 上一个 promise resolve(value)
this.resolvePromise(promise2, x, resolve, reject);
} catch (err) {
reject(err);
}
}, 0);
}
if (this.state === 'rejected') {
setTimeout(() => {
try {
const x = onRejected(this.reason);
this.resolvePromise(promise2, x, resolve, reject);
} catch (err) {
reject(err);
}
}, 0);
}
if (this.state === 'pending') {
this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
this.resolvePromise(promise2, x, resolve, reject);
} catch (err) {
reject(err);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason);
this.resolvePromise(promise2, x, resolve, reject);
} catch (err) {
reject(err);
}
}, 0);
});
}
});
return promise2;
}
resolvePromise(promise2, x, resolve, reject) {
console.log('resolvePromise...')
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise'));
}
let called = false;
if (x instanceof MyPromise) {
x.then(
value => {
if (called) return;
called = true;
this.resolvePromise(promise2, value, resolve, reject);
},
reason => {
if (called) return;
called = true;
reject(reason);
}
);
} else {
resolve(x);
}
}
catch(onRejected) {
return this.then(null, onRejected);
}
static resolve(value) {
return new MyPromise((resolve) => {
resolve(value);
});
}
static reject(reason) {
return new MyPromise((_, reject) => {
reject(reason);
});
}
}
这是一个简单的 MyPromise
实现,它模拟了 JavaScript 中的 Promise 对象。让我解释一下它的主要部分:
-
构造函数:
- 构造函数接受一个执行器函数
executor
,该函数有两个参数resolve
和reject
,它们分别用于将 Promise 状态从 pending 改变为 fulfilled 或 rejected。 - 在构造函数中,我们初始化了 Promise 的状态为
pending
,以及定义了value
和reason
属性来保存解决或拒绝时的值或原因。 onResolvedCallbacks
和onRejectedCallbacks
分别用于存储通过then
方法注册的解决或拒绝处理函数。
- 构造函数接受一个执行器函数
-
then 方法:
then
方法用于注册解决或拒绝时的处理函数,并返回一个新的 Promise 对象。- 如果当前 Promise 的状态已经是
fulfilled
或rejected
,则通过setTimeout
来确保异步执行处理函数,以保持 Promise 的异步特性。 - 如果当前 Promise 的状态仍然是
pending
,则将处理函数推入对应的回调数组中,以便在状态改变时执行。
-
resolvePromise 方法:
resolvePromise
方法用于处理then
方法返回的新 Promise 对象的状态。- 它检查处理函数的返回值
x
,如果是一个 Promise,则递归处理其状态,直至解析出最终的值。 - 如果
x
是一个普通值,则直接将其传递给新 Promise 的resolve
方法。
-
catch 方法:
catch
方法用于注册拒绝时的处理函数,实际上是then
方法的语法糖,相当于then(null, onRejected)
。
-
静态方法:
Promise.resolve
和Promise.reject
分别返回一个已解决或已拒绝的 Promise 对象。Promise.all
接受一个 Promise 数组,并返回一个新的 Promise 对象,当数组中所有 Promise 都解决时,新 Promise 才解决,其值是一个包含所有解决值的数组。Promise.race
接受一个 Promise 数组,并返回一个新的 Promise 对象,只要数组中有一个 Promise 解决或拒绝,新 Promise 即解决或拒绝。
这就是这个简单 MyPromise
实现的核心部分。它提供了 Promise 对象的基本功能,但并不包含所有的特性,比如异步任务的调度、微任务队列等。
三、示例:重点学习
注意如下几种示例的区别,并通过具体实现分析内部执行顺序。
示例 1
const p = new MyPromise((resolve, reject) => {
resolve('123');
});
p.then(msg => {
console.log('[result]', msg);
})
执行结果:
分析:
- 第一个 MyPromise constructor 是
const p = new MyPromise
执行的,第二个是p.then
- 执行到 then 时,
this.state === 'fulfilled'
,具体代码:
if (this.state === 'fulfilled') {
setTimeout(() => {
try {
const x = onFulfilled(this.value); // this.value 为 上一个 promise resolve(value)
this.resolvePromise(promise2, x, resolve, reject);
} catch (err) {
reject(err);
}
}, 0);
}
会先执行 onFulfilled,即 p.then(msg => { console.log('[result]', msg); })
,把 123 打印出来,最后再执行 this.resolvePromise
,具体代码:
resolvePromise(promise2, x, resolve, reject) {
console.log('resolvePromise...')
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise'));
}
if (x instanceof MyPromise) {
//...
} else {
resolve(x);
}
}
即通过 resolve(x)
最后将 then 创建的第二个 promise resolve 状态从 pending 变为 fulfilled。
示例 2
const p = new MyPromise((resolve, reject) => {
reject('123');
});
p.then(msg => {
console.log('[result]', msg);
}, err => {
console.log('[reason]', err);
})
执行结果:
分析:
- 初始 promise 内部调用
reject('123')
,将执行如下逻辑:
if (this.state === 'rejected') {
setTimeout(() => {
try {
const x = onRejected(this.reason);
this.resolvePromise(promise2, x, resolve, reject);
} catch (err) {
reject(err);
}
}, 0);
}
- 同理,最后会执行
this.resolvePromise
,将 then 创建的第二个 promise resolve 状态从 pending 变为 fulfilled。
示例 3
我们知道 promise 实例除了支持 then,还支持 catch,如果同时使用?
const p = new MyPromise((resolve, reject) => {
reject('123');
});
p.then(msg => {
console.log('[result]', msg);
}, err => {
console.log('[reason1]', err);
})
p.catch(err => {
console.log('[reason2]', err);
})
执行结果:
分析:
- 通过上述具体实现知道,catch 本质上会调用
return this.then(null, onRejected);
,因此在执行结果可以看到执行了两次 then - 并且 then 里的 onRejected 和 catch 里的 onRejected 可以独立执行不影响
示例 4
什么情况下会执行到 then 里的:
if (this.state === 'pending') {}
示例代码如下:
const p = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('123');
}, 2000);
});
p.then(msg => {
console.log('[result]', msg);
})
执行结果:
分析:
- 可以发现
this.state === pending
,这是因为初始时的 promise 里是一个 setTimeout 异步逻辑,因此执行到 then 时会发现此时处于 pending - 这种情况具体实现:
if (this.state === 'pending') {
this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
this.resolvePromise(promise2, x, resolve, reject);
} catch (err) {
reject(err);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason);
this.resolvePromise(promise2, x, resolve, reject);
} catch (err) {
reject(err);
}
}, 0);
});
}
可以发现会先如果当前 Promise 的状态仍然是 pending
,则将处理函数推入对应的回调数组中,以便在状态改变时执行。
- 所以当 setTimeout 计时结束可以执行回调时,会执行:
const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.onResolvedCallbacks.forEach(fn => fn());
}
};
首先会变更状态 pending 为 fulfilled,然后从回调数组中取出之前推入的处理函数进行执行,从而输出 123
。
示例 5
我们都知道链式 then 调用,那究竟底层会发生什么?
示例代码如下:
const p = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('123');
}, 2000);
});
p.then(msg => {
console.log('[result]', msg);
return '456';
}).then(msg => {
console.log('[result]', msg);
})
执行结果:
- 可以发现有两次then,并且创建了两个新 promise 实例
- 会依次执行 then,并且在上一个 promise resolve 之后将
this.value
作为结果打印
示例 6
前面我们的 then 函数要么不做返回,要么返回一个简单的字符串。那假如我们在 then 里返回的是一个 promise 呢?
const p = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('123');
}, 2000);
});
p.then(msg => {
console.log('[result]', msg);
return new MyPromise((resolve, reject) => {
resolve('456');
})
}).then(msg => {
console.log('[result]', msg);
})
执行结果:
分析:
- 最开始两个
then...
是p.then().then()
执行得到的 - 当初始 promise 内的 setTimeout 计时结束执行回调时,成功将初始 promise 状态从 pending 变为 fulfilled,并且取出回调函数列表中推入的如下内容:
p.then(msg => {
console.log('[result]', msg);
return new MyPromise((resolve, reject) => {
resolve('456');
})
})
执行后会先打印 [result] 123
,接着打印 MyPromise constructor...
,即 return new MyPromise()
构造的。
- 紧接着执行了
this.resolvePromise
,注意这里x instance MyPromise
将为真,具体代码如下:
resolvePromise(promise2, x, resolve, reject) {
console.log('resolvePromise...')
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise'));
}
let called = false;
if (x instanceof MyPromise) {
x.then(
value => {
if (called) return;
called = true;
this.resolvePromise(promise2, value, resolve, reject);
},
reason => {
if (called) return;
called = true;
reject(reason);
}
);
} else {
//...
}
}
可以从上述代码看出,对于 return 的新 promise,会执行其 then,所以在截图中打印了第三个 then...
。只不过要注意, resolve 在 this.resolvePromise
是透传的,因此当内部 resolve 之后,第二个 then 能拿到 456
并打印成功。
示例 7
自己分析一下下面会如何执行吧。
const p = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('123');
}, 2000);
});
p.then(msg => {
console.log('[result]', msg);
return new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('456');
}, 4000);
})
}).then(msg => {
console.log('[result]', msg);
})