资讯专栏INFORMATION COLUMN

从零实现一个简易的 Promise

WilsonLiu95 / 1404人阅读

从零实现一个简易的 Promise
所有问题都可以通过加一层中间层来解决。

Promises/A+

简易的,不做废话直接开始 :)

const p = new Promise((resolve, reject)=>{ 
    // 如果操作成功则调用 resolve 并传入 value
    // 如果操作失败则调用 reject 并传入 reason
});

通常我们都会使用上述方法获取 Promise 实例:在构造函数种传入一个 executor 方法,当同步/异步的任务完成时调用 resolve,失败时调用 reject,简单易懂,在此不多赘述。

状态机

一个 Promise 可以理解为一个状态机,相应的 API 接口要么用于改变状态机的状态,要么在到达某个状态时被触发,因此首先需要实现的是 Promise 的状态信息:

const PENDING = 0
const FULFILLED = 1
const REJECTED = 2

并且只存在 PENDING => FULFILLED 或者 PENDING => REJECTED 的状态转移。

构造函数

首先实现构造函数的框架如下:

class Promise {
    constructor(executor) {
        this.status = PENDING; // 实例当前的状态
        this.data = undefined; // Promise 返回的值
        this.defered = []; // 回调函数集
        executor(resolve, reject); // 执行 executor 并传入相应的参数
    }
}

上述代码基本实现 Promise 构造函数的主题部分,但是存在三个问题:

resolve 和 reject 参数/方法尚未定义

executor 函数体中可能会抛出异常,需要做容错处理

考虑 executor 函数体中在调用 resolve/reject 时的 this 指向问题

修修补补如下:

class Promise {
    constructor(executor) {
        this.status = PENDING;
        this.data = undefined;
        this.defered = [];

        try {
            // bind, bind, bind!
            executor(this.resolve.bind(this), this.reject.bind(this));
        } catch (e) {
            this.reject(e);
        }
    }

    resolve(value) {
        // TODO
    }

    reject(reason) {
        // TODO
    }
}
resolve & reject

接下来实现 resolve 和 reject 方法,基本上就是在判断状态为 PENDING 之后把状态改为相应的值,并把对应的 value 和 reason 存在 self 的 data 属性上面,最后执行相应的回调函数,逻辑很简单:

resolve(value) {
    if (this.status === PENDING) {
        this.status = FULFILLED;
        this.data = value;
        this.defered.forEach(i => i.onfulfiled(value));
    }
}

reject(reason) {
    if (this.status === PENDING) {
        this.status = REJECTED;
        this.data = reason;
        this.defered.forEach(i => i.onrejected(reason));
    }
}
then 方法

Promise 对象有一个 then 方法,用来注册在这个 Promise 状态确定后的回调,很明显 then 方法需要写在原型链上,Promise 总共有三种可能的状态,在 then 方法中我们分别用三个判断分支来处理,并且都分别返回一个新的 Promise 实例。

then(onResolved, onRejected) {
    // 如果 then 的参数不是 function 则我们需要忽略它
    onResolved = typeof onResolved === "function" ? onResolved : function(v) {};
    onRejected = typeof onRejected === "function" ? onRejected : function(r) {};

    switch (this.status) {
        case FULFILLED:
            return new Promise((resolve, reject) => {
                // TODO
            });

        case REJECTED:
            return new Promise((resolve, reject) => {
                // TODO
            });

        case PENDING:
            return new Promise((resolve, reject) => {
                // TODO
            });
    }
}

完整的实现如下,其中需要注意的是,如果 onResolved 的返回值是一个 Promise 对象,则直接取它的结果做为新的 Promise 实例的结果

then(onResolved, onRejected) {
    onResolved = typeof onResolved === "function" ? onResolved : function(v) {};
    onRejected = typeof onRejected === "function" ? onRejected : function(r) {};

    switch (this.status) {
        case FULFILLED:
            return new Promise((resolve, reject) => {
                try {
                    const r = onResolved(this.data);
                    r instanceof Promise && r.then(resolve, reject);
                    resolve(r);
                } catch (e) {
                    reject(e);
                }
            });

        case REJECTED:
            return new Promise((resolve, reject) => {
                try {
                    const r = onRejected(this.data);
                    r instanceof Promise && r.then(resolve, reject);
                } catch (e) {
                    reject(e);
                }
            });

        case PENDING:
            return new Promise((resolve, reject) => {
                const onfulfiled = () => {
                    try {
                        const r = onResolved(this.data);
                        r instanceof Promise && r.then(resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                };

                const onrejected = () => {
                    try {
                        const r = onRejected(this.data);
                        r instanceof Promise && r.then(resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                };

                this.defered.push({
                    onfulfiled,
                    onrejected
                });
            });
    }
}

至此实现一个简易的 Promise,使用如下测试用例验证:

new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(1);
    }, 1000);
}).then((res) => {
    console.log(res);
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(2);
        }, 1000);
    });
}).then((res) => {
    console.log(res);
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(3);
        }, 1000);
    });
}).then((res) => {
    console.log(res);
});

// 1
// 2
// 3
// [Finished in 3.1s]
其他问题(UPDATED) 异步问题
new Promise((resolve) => {
        resolve();
    })
    .then(() => {
        console.log("1");
    })
    .then(() => {
        console.log("2");
    });

console.log("3");

执行上面的代码会发现输出的顺序是“1, 2, 3”,而不是正确的“3, 1, 2”,显然是因为我们没有在 Promise 的 resolve 方法中异步的调用回调函数集导致的,当然解决这个问题也很简单,就是使用 setTimeout,但是这样实现的话并不符合 Promise 在事件循环中的优先级,所以暂时忽略。

值穿透问题
new Promise((resolve) => {
        resolve(8);
    })
    .then()
    .then()
    .then((value) => {
        console.log(value)
    });

上面的代码使用我们刚刚实现的 Promise 会打印 undefined,然而这并不是我们期望得到的结果,我们希望的是8这个值会穿过两个 then 到达链尾的 then 的执行函数里,其输出应该和这段代码一致:

new Promise((resolve) => {
        resolve(8);
    })
    .then((value) => {
        return value;
    })
    .then((value) => {
        return value;
    })
    .then((value) => {
        console.log(value);
    });

其实要实现这个功能十分简单,只要把 then 的两个参数的默认值做简单的修改:

onResolved = typeof onResolved === "function" ? onResolved : function(v) { return v; };
onRejected = typeof onRejected === "function" ? onRejected : function(r) { return r; };
Promise 就是充当一个中间层,把回调造成的控制反转再反转回去。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/94843.html

相关文章

  • 从零开始实现一个简易Java MVC框架

    摘要:不过仔细了解了一段时候发现,其实他的原理是很简单的,所以想要自己也动手实现一个功能类似的框架。原文地址从零开始实现一个简易的框架 前言 最近在看spring-boot框架的源码,看了源码之后更是让我感受到了spring-boot功能的强大。而且使用了很多的设计模式,让人在看的时候觉得有点难以下手。 不过仔细了解了一段时候发现,其实他的原理是很简单的,所以想要自己也动手实现一个功能类似的...

    neuSnail 评论0 收藏0
  • 从零开始实现一个简易Java MVC框架(五)--引入aspectj实现AOP切点

    摘要:接下来就可以把这个切点类加入到我们之前实现的功能中了。实现的切点功能首先改装注解,把之前改成来存储表达式。测试用例在上一篇文章从零开始实现一个简易的框架四实现中的测试用例的基础上修改测试用例。 前言 在上一节从零开始实现一个简易的Java MVC框架(四)--实现AOP中我们实现了AOP的功能,已经可以生成对应的代理类了,但是对于代理对象的选择只能通过指定的类,这样确实不方便也不合理。...

    wupengyu 评论0 收藏0
  • 从零开始实现一个简易Java MVC框架(六)--加强AOP功能

    摘要:在前面的文章中实现的功能时,目标类都只能被一个切面代理,如果想要生成第二个代理类,就会把之前的代理类覆盖。改装原有功能现在要改装原来的的实现代码,让的功能加入到框架中为了让切面能够排序,先添加一个注解,用于标记排序。 前言 在前面从零开始实现一个简易的Java MVC框架(四)--实现AOP和从零开始实现一个简易的Java MVC框架(五)--引入aspectj实现AOP切点这两节文章...

    Loong_T 评论0 收藏0
  • 从零开始实现一个简易Java MVC框架(八)--制作Starter

    摘要:服务器相关配置启动类资源目录目录静态文件目录端口号目录目录实现内嵌服务器在上一章文章从零开始实现一个简易的框架七实现已经在文件中引入了依赖,所以这里就不用引用了。 spring-boot的Starter 一个项目总是要有一个启动的地方,当项目部署在tomcat中的时候,经常就会用tomcat的startup.sh(startup.bat)的启动脚本来启动web项目 而在spring-b...

    AprilJ 评论0 收藏0
  • 从零开始实现一个简易Java MVC框架(九)--优化MVC代码

    摘要:前言在从零开始实现一个简易的框架七实现中实现了框架的的功能,不过最后指出代码的逻辑不是很好,在这一章节就将这一部分代码进行优化。 前言 在从零开始实现一个简易的Java MVC框架(七)--实现MVC中实现了doodle框架的MVC的功能,不过最后指出代码的逻辑不是很好,在这一章节就将这一部分代码进行优化。 优化的目标是1.去除DispatcherServlet请求分发器中的http逻...

    ruicbAndroid 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<