关于javascript:用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) => {
        queueMicrotask(() => { // 新建微工作
            if (this.status === 'pending') {
                this.status = 'fulfiled';
                this.value = data;
                this.resolveList.forEach((resFunc) => resFunc(data));
                this.resolveList.length = 0;
            }
        });
    };
    
    const reject = (data) => {
        queueMicrotask(() => { // 新建微工作
            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办法, 定义在原型链上, 返回一个promise
  • 分三种状态解决,pending时仅把回调传入同时promise化,fulfilled或failed时间接执行,并promise返回
  • 这里能够看出下面形容的,如果resCb或rejCb传入为空,会将promise的value传递给返回的promise
  • 并且如果cb返回的是promise,会以那个promise的res或rej触发下层的res或rej,同时cb中的data值是内层promise的value
myPromise.prototype.then = function (resCb, rejCb) {
    if (this.status === 'pending') {
        return new myPromise((res, rej) => {
            this.resolveList.push(function () {
                // 如果没有定义回调,将值传递给下一个
                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);
                }
            });
            this.rejectList.push(function () {
                // 如果没有定义回调,将值传递给下一个
                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);
                }
            });
        });
    } else if (this.status === 'fulfiled) {
        return new myPromise((res, rej) => {
            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)
            }
        });
    } else if (this.status === 'failed') {
        return new myPromise((res, rej) => {
            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)
            }
        });
    }
};
  • catch办法,即then的语法糖
myPromise.prototype.catch = (rej) => myPromise.prototype.then(null, rej);
  • 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办法,返回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的更新,例子如下:
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);
        });
    });
}
  • reslove和reject办法,其实就是裸露外部定义的reslove和reject
myPromise.prototype.reslove = (data) => {
    return new myPromise((resolve, reject) => {
        resolve(data);
    });
};

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

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理