Promise可以说是面试的一个非常重要的点了,可以考的东西非常多,但是如果当你真正掌握了源码逻辑,那么才算真正彻底搞懂所有Promise类型的题目了,而且大厂面试过程中手写Promise源码也是挺常见的,所以本文总结如何理解Promise源码,让我们手写的时候理清头绪
直接上源码,主要的一些思路都写在注释里了。我们需要写的源码实际上就是一个Promise类的封装,我们这里采用ES6的class声明类,以下是我们需要声明和实现的
构造函数
constructor(executor)- 初始化实例属性
- 声明
resolve和reject方法 - 执行
executor并传入2中的两个方法,执行过程需要被try...catch包裹,出错跳reject
实例属性
state:只有三种值,且只会从1跳到2或3pendingfulfilledrejected
value:resolve函数的参数reason:reject函数的参数onResolvedCallbacks:resolve后的回调函数数组onRejectedCallbacks:reject后的回调函数数组 原型方法then:需要满足一下场景- 同一个promise可以多次调用
- 可以链式调用
catch- excuetor执行过程中出错会跳到这
- then函数中reject参数未传时跳到这 静态方法
allraceresolvereject
先来个API目录版的
class Promise{
constructor(executor){}
then(resolve,reject){}
catch(fn){}
static resolve(val){}
static reject(reason){}
static all(promiseArr){}
static race(promiseArr){}
}
// 对then的返回值做处理,单独封装
function resolvePromise(promise2,x,resolve,reject){}
简单完整源码
class Promise{
constructor(executor){
// 初始化参数
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
// 为什么需要保存回调函数?因为Promise初始化的函数可能是异步的,可以通过事件监听,将两种状态变化的回调函数用两个数组保存
// 为什么是数组?因为同一个Promise可以调用多次then函数,意味着回调函数可能不止一个
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
// 这个两个函数实际上是作为参数预先传入到生成函数中的,当在生成器中执行这两个函数时
// 说明state发生变化了,且需要调回调函数
let resolve = value => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
// resolve的回调函数依次执行
this.onResolvedCallbacks.forEach(fn=>fn());
}
};
let reject = reason => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
// reject的回调函数依次执行
this.onRejectedCallbacks.forEach(fn=>fn());
}
};
// 整个执行过程中都是被try...catch包裹
try{
// 直接执行了executor,这也就说明了为什么
// new Promise(fn(reslove,reject){})中的函数是同步被执行的
executor(resolve, reject);
} catch (err) {
// 执行过程中有错误就会跳到reject,这里执行捕获到同步代码的错误
reject(err);
}
}
then(onFulfilled,onRejected) {
// 这里需要考虑到我们调用then的时候参数缺省或类型不是function的情况
// 需要由一个默认的函数
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
// 由于then是可以链式调用的,这就决定了他的返回值也是一个promise
let promise2 = new Promise((resolve, reject) => {
// 前两个if是有可能在executor中已经同步执行了resolve或reject
// 也有可能是then是被异步调用的,在调用时state已经发生了变化,这种情况下直接执行该回调就完事了
if (this.state === 'fulfilled') {
// 但是这种执行也不是直接执行的而是异步的,
// 我们这里用setTimeout来代替这种异步,
// 但我们知道promise是个微任务,而setTimeout是个宏任务,这里知道就行了,可以用别代替
setTimeout(() => {
try {
// 用我们传的函数来处理这个value,也就是我们一般定义的参数名res
// 为啥要保存这个执行完的返回值x呢?因为如果这个返回值就是promise的实例的话那就返回他就行了,下面这个函数就是来判断这个的
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
};
// 同上
if (this.state === 'rejected') {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
};
// 因为executor中的函数一般是异步的,所以我们调用then函数的时候还没有执行到resolve或reject
// 这也就意味着state还是pending
if (this.state === 'pending') {
// 往回调函数数组里push,但是需要和上面一样封装一下
this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});
// 同上
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0)
});
};
});
return promise2;
}
// 实际上是一个then的语法糖,这样就非常好理解了
catch(fn){
return this.then(null,fn);
}
//resolve方法
static resolve(val){
return new Promise((resolve,reject)=>{
resolve(val)
});
}
//reject方法
static reject(val){
return new Promise((resolve,reject)=>{
reject(val)
});
}
//race方法
static race(promises){
return new Promise((resolve,reject)=>{
for(let i=0;i<promises.length;i++){
promises[i].then(resolve,reject)
};
})
}
//all方法(获取所有的promise,都执行then,把结果放到数组,一起返回)
static all(promises){
let arr = [];
let i = 0;
function processData(index,data){
arr[index] = data;
// 每当有一个好了就+1,直到所有的都fulfilled
i++;
if(i == promises.length){
resolve(arr);
};
};
return new Promise((resolve,reject)=>{
for(let i=0;i<promises.length;i++){
promises[i].then(data=>{
processData(i,data);
},reject);
};
});
}
}
function resolvePromise(promise2, x, resolve, reject){
// 防止死循环
if(x === promise2){
return reject(new TypeError('Chaining cycle detected for promise'));
}
let called;
// 这里主要是分x是否是一个promise(有无then属性)两种情况讨论
// 如果x不是promise,将其直接传到下一个then的res参数
// 如果x是promise
if (x != null && (typeof x === 'object' || typeof x === 'function')) {
try {
// 这里就是当x是promise的情况,就递归调用resolvePromise
// 解释一下就是,如果一直找到返回值不是promise为止才,再把那个值返回出来
if (x instancof Promise) {
x.then(y => {
if(called)return;
called = true;
resolvePromise(promise2, y, resolve, reject);
}, err => {
if(called)return;
called = true;
reject(err);
})
} else {
resolve(x);
}
} catch (e) {
if(called)return;
called = true;
reject(e);
}
} else {
resolve(x);
}
}
总结
- 构造函数中需要初始化好参数,定义好
resolve和reject,主要是修改状态,保存value或reason,依次执行回调,调用执行器。 then的实现需要注意链式调用必然需要返回一个promise实例,这个实例resolve有一个条件,如果当前的回调函数的返回值是一个promise实例,那么他会等这个promise的实例的resolve,相当于是一个递归,也就意味着下一个then的值res或err必定不会是一个promise;then中resolve执行的时候出错都会传递给下一个reject。- catch实际上一个语法糖,只有
reject而没有resolve - 4个静态方法都比较简单,不多做解析了