用JS实现Promise类

Promise

  • 其实就是一个类,内部保存着回调队列,通过暴露resolve和reject方法触发对应回调
  • 内部有三种状态值,pending,fulfiled,failed
  • Promise是微任务,与settimout等宏任务有差别,执行顺序时是,先主线程,然后微任务,然后宏任务
  • 有以下几种方法:then, catch, all, race, resolve, reject
  • 一旦实例了一个Promise,其就一定会执行,无法停止
  • Promise中,由于报错也会触发reject,所以不会中断程序
  • catch其实是then的语法糖,相当于then(null,rej),即成功回调传了空
  • 如果then中,没有传入回调,则promise的resolve或reject值会传递到下一个then或catch中,如下:
const p = new Promise((res) => res(2));
p.then(null).then((data) => console.log(data)); // 2
p.then(() => {}).then((data) => console.log(data)); // undefined
p.catch((data) => {console.log('catch', data)}).then((data) => console.log('then', data)); // then, 2

实现

内部变量

function myPromise(func) {
    this.status = 'pending'; // 状态
    this.value = undefined; // 值
    this.resolveList = []; // 成功回调
    this.rejectList = []; // 失败回调
}

内部resolve和reject方法

  • 同时执行传入的func
function myPromise(func) {
    this.status = 'pending'; // 状态
    this.value = undefined; // 值
    this.resolveList = []; // 成功回调
    this.rejectList = []; // 失败回调
    
    const resolve = (data) => {
        if (this.status === 'pending') {
            this.status = 'fulfiled';
            this.value = data;
            this.resolveList.forEach((resFunc) => resFunc(data));
            this.resolveList.length = 0;
        }
    };
    
    const reject = (data) => {
        if (this.status === 'pending') {
            this.status = 'failed';
            this.value = data;
            this.rejectList.forEach((resFunc) => resFunc(data));
            this.rejectList.length = 0;
        }
    };
    
    if (typeof func === 'function') {
        try {
            func(resolve, reject); // promise传入的func是马上执行的,但回调resolve和reject是异步微任务
        } catch(err) {
            reject(err);
        }
    }
}

then方法

  • then方法, 定义在原型链上, 返回一个promise
  • 分三种状态处理,pending时仅把回调添加到回调队列,同时promise化,fulfilled或failed时直接执行,并返回promise
function handleResolve(res, rej, resCb, rejCb) {
    queueMicrotask(() => {
        // 如果没有定义回调,将值传递给下一个
        try {
            const ret = typeof resCb === 'function' ? resCb(this.value) : this.value;
            if (ret instanceof myPromise) {
                ret.then(res, rej);
            } else {
                res(ret);
            }   
        } catch (err) {
            rej(err);
        }
    });
}

function handleReject(res, rej, resCb, rejCb) {
    queueMicrotask(() => {
        // 如果没有定义回调,将值传递给下一个
        try {
            const ret = typeof rejCb === 'function' ? rejCb(this.value) : this.value;
            if (ret instanceof myPromise) {
                ret.then(res, rej);
            } else {
                rej(ret);
            }   
        } catch (err) {
            rej(err);
        }
    });
}

myPromise.prototype.then = function (resCb, rejCb) {
    if (this.status === 'pending') {
        return new myPromise((res, rej) => {
            this.resolveList.push(() => {
                handleResolve(res, rej, resCb, rejCb));
            });
            this.rejectList.push(() => {
                handleReject(res, rej, resCb, rejCb));
            });
        });
    } else if (this.status === 'fulfiled') {
        return new myPromise((res, rej) => {
            handleResolve(res, rej, resCb, rejCb));
        });
    } else if (this.status === 'failed') {
        return new myPromise((res, rej) => {
            handleReject(res, rej, resCb, rejCb));
        });
    }
};
  • 这里看到如果resCb或rejCb传入为空,会将promise的value传递给返回的promise
  • 并且如果cb返回的是promise,会以那个promise的res或rej触发上层的res或rej,同时cb中的data值是内层promise的value
  • 例子如下:
const p1 = new Promise((res) => res(1));
p1.then(null).then((data) => console.log(data));
p1.then(() => {}).then((data) => console.log(data));

const p2 = new Promise((res, rej) => rej(2));
p2.then(() => {}).catch((err) => console.log(err));
p2.then(() => {}, () => {}).catch((err) => console.log(err));

/*
输出如下:
1
undefined
2
undefined
*/
  • 即如果promise的resolve或reject没有在then中传入对应的回调函数,会将其value不断的传递下去

微任务创建时机

  • 从reslove以及reject定义可以看到,其仅仅只是改变promise的状态以及其value,那么微任务,即异步是在哪创建的呢?注意下面的例子有:
const p = new Promise((res) => res(1));
p.then(() => console.log('p1 then'))
 .then(() => console.log('p1 then then'));
const p2 = new Promise((res) => res(2));
p2.then(() => console.log('p2 then'));

/*
输出如下:p1 then -> p2 then -> p1 then then
*/
  • 微任务在then中创建,回到then方法的定义中,可以看到handleResolve以及handleReject,利用了queueMicrotask创建微任务
  • 结合上诉例子以及then方法,可以看到

    • 当p1.then注册了一个回调函数并把他放在微任务后,返回了一个promise
    • 但该返回的promise中的resolve是放在微任务中的,即当前不执行then返回promise的reslove
    • 所以在返回的promise中用then注册的回调函数,是放到resolveList中的,也就是当前不执行
    • 然后到p2.then,其也产生了一个微任务
    • 当前微任务队列有两个任务,先执行p1 then的输出,然后执行p1.then返回promise的resolve,又新建了一个微任务,放到微任务队列
    • 然后执行p2 then,p1 then then

catch方法

  • catch方法,即then的语法糖
myPromise.prototype.catch = (rej) => myPromise.prototype.then(null, rej);

all方法

  • all方法,传入一个数组,输出一个promise,全部resolve时,输出promise状态变为fulfiled,触发resolve
  • 当任何一个传入的promise失败时,输出promise状态变failed,触发reject,但并不会终止其他promise继续执行,只是其他promise的执行已经不会影响promise.all返回promise的状态了
myPromise.prototype.all = (iterable) => {
    const len = iterable.length;
    const result = [];
    return new myPromise((resolve, reject) => {
        let index = 0;
        iterable.forEach((item) => {
            item.then((data) => {
                result.push(data);
                index++;
                if (index === len) resolve(result);
            }, (err) => {
                reject(err);
            });
        });
    });
}

race方法

  • race方法,返回promise数组中第一个执行完毕的状态,无论是fulfiled还是failed,但不影响数组中其余promise继续执行
myPromise.prototype.race = (iterable) => {
    let done = false;
    return new myPromise((resolve, reject) => {
        iterable.forEach((item) => {
            item.then((data) => {
                if (!done) {
                    done = true;
                    resolve(data);
                }
            }, (err) => {
                if (!done) {
                    done = true;
                    reject(err);
                }
            });
        });
    });
}
  • 其中上面的done标志可以去掉,因为promise中,resolve两次,只会在首次执行then函数,具体原因是,第一次resolve时,执行resolveList中的回调函数,执行完毕后会把其清零,即如果没有在执行完毕后再注册新的then方法,第二次resolve不会触发回调
  • 另外即使在第二次reslove后新注册了一个then,其获取的值也是第一次resolve的值,而不是第二次resolve的值,这是因为更新promise内的value只会发生在promise状态从pending变为fulfiled或failed的时候,而第一次resolve已经让状态变了,不会再触发value的更新,即promise的状态会凝固,例子如下:
const p1 = new Promise((res) => {
    setTimeout(() => { console.log('res(1)'); res(1)}, 1000);
    setTimeout(() => { console.log('res(2)'); res(2)}, 3000);
});

p1.then((data) => console.log(data));

setTimeout(() => {
    console.log('then again');
    p1.then((data) => console.log(data));
}, 5000);

/* 
输出 res(1) -> 1  -> res(2) -> then again -> 1
最后输出1而不是2
*/
  • 回到race,上述race方法可简化写成如下
myPromise.prototype.race = (iterable) => {
    return new myPromise((resolve, reject) => {
        iterable.forEach((item) => {
            item.then(resolve, reject);
        });
    });
}

外部resolve和reject方法

  • reslove和reject方法,其实就是暴露内部定义的reslove和reject,返回一个promise
myPromise.prototype.reslove = (data) => {
    return new myPromise((resolve, reject) => {
        resolve(data);
    });
};

myPromise.prototype.reject = (data) => {
    return new myPromise((resolve, reject) => {
        reject(data);
    });
};

你可能感兴趣的:(javascript,前端)