手写简单版promise (一)

·  阅读 188

这是我参与8月更文挑战的第3天,活动详情查看:8月更文挑战

前言

本文为手写一个基础版 promise ,用于理解其常用方法。涉及的方法有 then、all、race、resolve、reject、finally、catch,其中 all、race、resolve、reject为静态方法即在方法名前面添加 static 关键字(在本文中先实现then、all)

创建 promise

使用 class 关键字来声名类,设置两个属性 promiseState、promiseResult,初始值分别为 pending 和 undefined;在构造函数中接收一个函数类型参数,该参数函数也可以接收两个参数 resolve 和 reject,分别用于成功的回调和失败的回调。

// 设置全局状态值,便于管理
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECT = 'reject';

class MyPromise {
    PromiseState = PENDING;
    PromiseResult = undefined;

    constructor (exectutor) {
        try {
            exectutor(this.resolve.bind(this), this.reject.bind(this));
        } catch (err) {
            this.reject(err);
        }
    }

    resolve (val) {
        if (this.PromiseState !== PENDING) return; // 状态需要变更时,当前状态必须为pending
        this.PromiseState = FULFILLED;
        this.PromiseResult = val;
    }

    reject (reason) {
        if (this.PromiseState !== PENDING) return;
        this.PromiseState = REJECT;
        this.PromiseResult = reason;
    }
}
复制代码

then 方法

定义</br>
1,接受两个回调函数</br>
2,如果状态变为成功状态,则执行第一个回调</br>
3,如果状态变为失败状态,则执行第二个回调</br>
4,如果状态变为待定状态,则保留当前的两个回调</br>

then 会返回一个新的 Promise 对象,该对象的状态和结果由回调函数的返回值决定</br>
如果返回值是 promise 对象,</br>
    返回值为成功,新promise 就是成功</br>
    返回值为失败,新promise 就是失败</br>
如果返回值非promise对象</br>
    新promise就是成功,它的值就是返回值</br>
特别是,如果第二个回调不为函数时,新 promise 一定是失败的状态,值为错误信息</br>
复制代码

首先对传入的回调函数经行类型检查,判断它们是否为函数。这里使用 Object.prototype.toString.call 可以检测到箭头函数,如果是 typeof 的话就检测不出箭头函数。对于第一个回调,如果不是函数的话,可以直接提供一个将入参直接返回的箭头函数;对于第二个回调,如果不是函数的话,要让返回的 promise 为失败状态,直接抛出异常即可,异常值则为传入的值。然后由于要返回一个 promise 对象,定义一个新的 promise 对象用于返回,由于在每个条件下具体代码的逻辑差不多,重复率较高,所以提一个公共方法出来;在这个公共方法中接收同状态下执行的不同回调,具体针对新 promise 状态进行逻辑处理。

以成功状态时调用第一个回调为例,可以先对回调的结果做判断:定义一个变量接收回调的返回值,若返回值是一个 promise 对象,则通过它的 then 方法来间接执行新 promise 的 resolve 或 reject 以改变新 promise 状态;若返回不是 promise 对象,则新 promise 的状态为成功,故直接执行其(新 promise) resolve方法。由于 then 方式是链式调用,故不应该出现 then 前面的 promise 与后面的 promise 完全相同的情况,因而添加一个条件判断 if (x === thenPromise) ,在这个条件判断中,由于要在 thenPromise 中使用它自己,那么就要保证这个时候 thenPromise 是有值的,就通过 queueMicrotask 方法实现一个异步操作;再者使用 try catch 来进行错误处理,有错误就直接调用新 promise 的 reject。

错误状态时调用第二个回调与其逻辑基本相同。待定状态时要保留传入的两个回调,在合适的时候触发,故设置两个对应的数组属性(fulfilledCbs、rejectCbs)用来储存这两个回调,合适的时候就是执行 resolve 或 reject 时,此刻依次调用数组项方法即可。

then (onFulfilled, onRejected) {
    // 参数判断
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val;
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };
    const thenPromise = new MyPromise((resolve, reject) => {
        const resolvePromise = cb => {
            queueMicrotask(() => { // 异步方法,让 thenPromise 先进行赋值操作
                try {
                    let x = cb(this.PromiseResult);
                    if (x === thenPromise) {
                        throw new Error('then lian 错误');
                    }
                    if (x instanceof MyPromise) {
                        x.then(resolve, reject);
                    } else {
                        resolve(x);
                    }
                } catch (err) {
                    console.error(err);
                    reject(err);
                }
            });
        };
        if (this.PromisState === FULFILLED) {
            resolvePromise(onFulfilled);
        } else if (this.PromisState === REJECT) {
            resolvePromise(onRejected);
        } else {
            this.fulfilledCbs.push(resolvePromise.bind(this, onFulfilled));
            this.rejectCbs.push(resolvePromise.bind(this, onFulfilled));
        }
    });
    return thenPromise;
}

// class MyPromise
fulfilledCbs = [];
rejectCbs = [];

// resolve 方法
while (this.fulfilledCbs.length) {
    this.fulfilledCbs.shift()(this.PromiseResult);
}

// reject 方法
while (this.rejectCbs.length) {
    this.rejectCbs.shift()(this.PromiseResult);
}
复制代码

all 方法

是一个静态方法,需要一个数组作为参数,返回一个 Promise</br>
参数数组中,如果所有 promise 对象都为成功,则返回成功状态的 promise对象</br>
参数数组中,如果有一个 promise 对象为失败,则返回失败状态的 promise对象,且中止执行后续 promise 对象</br>
复制代码

定义一个用于收集成功 promise 结果的数组 result,然后参数数组通过 foreach 来处理每一项。首先判断当前项是否为一个 promise ,是的话就通过其 then 方法来确定其状态,如果是成功的回调就将结果存入 result,如果是失败的回调则直接调用新 promise 的 reject 设置状态;如果当前项不是 promise 则直接将其加入到 result 数组中。最后判断 result 长度是否和参数数组相同,相同就调用新 promise 的 resolve 设置状态。

static all (arr) {
    let result = [];
    const allPromise = new MyPromise((resolve, reject) => {
        arr.forEach(item => {
            if (item instanceof MyPromise) {
                item.then(val =>{
                    addData(val);
                }, reject);
            } else addData(item);
        });
        function addData (val) {
            result.push(val);
            if (result.length === arr.length) resolve(result);
        }
    });
    return allPromise;
}
复制代码

参考

# 「景水」- 手写 Promise 心得分享
本文就先到此打住,剩下的几个方法在明天的文章中将进行讲述。源码在这里

分类:
前端
标签:
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改