资讯专栏INFORMATION COLUMN

手写一个符合A+规范的Promise

jsummer / 3369人阅读

摘要:本文同时也发布在我的博客上,欢迎之前也手写过简单的,这次则是为了通过官方的测试集,借鉴了一些下载量较多的,改了几遍,终于是通过了规范的个测试用例如何测试测试库地址在这,大家在写完自己的后,不妨也去测试一下,检验自己的是否符合规范。

本文同时也发布在我的github博客上,欢迎star~

之前也手写过简单的promise,这次则是为了通过官方的Promise A+测试集,借鉴了一些下载量较多的promise polyfill,改了几遍,终于是通过了A+规范的872个测试用例

如何测试?

测试库地址在这:promises-tests ,大家在写完自己的promise后,不妨也去测试一下,检验自己的promise是否符合Promise A+规范。这个库使用起来很方便,像下面这样就可以了:

const tests = require("promises-aplus-tests");
const Promise = require("./index");

const deferred = function() {
    let resolve, reject;
    const promise = new Promise(function(_resolve, _reject) {
        resolve = _resolve;
        reject = _reject;
    });
    return {
        promise: promise,
        resolve: resolve,
        reject: reject
    };
};
const adapter = {
    deferred
};
tests.mocha(adapter);

其中,index.js中是你写的Promise

实现

首先我们定义一些全局属性:

const IS_ERROR = {};
let ERROR = null;

IS_ERROR作为发生错误时的标识,ERROR用来保存错误;

做好准备工作,再来定义_Promise类,其中fn是Promise接受的函数,构造函数执行时立刻调用;_status是Promise的状态,初始为0(pending),resolved时为1,rejected时为2;_value用来保存Promise resolved时的返回值和rejected时的失败信息;_handlers用来保存Promise成功和失败时调用的处理方法

function _Promise(fn) {
    this._status = 0;
    this._value = null;
    this._handlers = [];
    doFn(this, fn);
}

最后执行doFn方法,传入this值和fn:

function doFn(self, fn) {
    const ret = safeCallTwo(
        fn,
        function(value) {
            self.resolve(value);
        },
        function(reason) {
            self.reject(reason);
        }
    );
    if (ret === IS_ERROR) {
        self.reject(ERROR);
    }
}

其中safeCallTwo是用来安全执行两参数方法的函数,当执行出错时,捕获错误,保存在ERROR中,返回IS_ERROR标识:

function safeCallTwo(fn, arg1, arg2) {
    try {
        return fn(arg1, arg2);
    } catch (error) {
        ERROR = error;
        return IS_ERROR;
    }
}

doFn中,调用safeCallTwo,fn传入两个参数供我们调用,也就是我们常用的resolve方法和reject方法,并获取到返回值,如果ret为错误标识IS_ERROR,则调用reject

_Promise原型上挂载着resolve和reject方法,如下:

_Promise.prototype.resolve = function(value) {
    if (this._status !== 0) {
        return;
    }
    this._status = 1;
    this._value = value;
    doThen(this);
};
_Promise.prototype.reject = function(reason) {
    if (this._status !== 0) {
        return;
    }
    this._status = 2;
    this._value = reason;
    doThen(this);
};

因为Promise的状态只能由pending转为resolvedrejected,所以在执行resolve和reject方法时,要先判断status是否为0,若不为0,直接return;修改status和value后,执行doThen方法:

function doThen(self) {
    const handlers = self._handlers;
    handlers.forEach(handler => {
        doHandler(self, handler);
    });
}

doThen函数的作用是从self上取出的handlers并依次执行

我们再来看一看挂载在原型上的then方法:

_Promise.prototype.then = function(onResolve, onReject) {
    const res = new _Promise(function() {});
    preThen(this, onResolve, onReject, res);
    return res;
};

我们知道,Promise是支持链式调用的,所以我们的then方法也会返回一个Promise,以供后续调用;

下面是preThen方法:

function preThen(self, onResolve, onReject, res) {
    onResolve = typeof onResolve === "function" ? onResolve : null;
    onReject = typeof onReject === "function" ? onReject : null;
    const handler = {
        onResolve,
        onReject,
        promise: res
    };
    if (self._status === 0) {
        self._handlers.push(handler);
        return;
    }
    doHandler(self, handler);
}

preThen方法接受4个值,分别为当前Promise——self,resolve后的回调函数onResolve,reject后的回调函数onReject,then函数返回的promise——res。先判断onResolve和onReject是否为函数,若不是,直接置为null。再将onResolve、onReject、res放入handler对象中

接下来需要注意,Promise接受的函数(也就是上文的fn)并不是一定是异步调用resolve和reject,也有可能是同步的,也就是说在执行preThen函数时,self的status可能已经不为0了,这时候我们就不需要将handler保存起来等待调用,而是直接调用回调函数

doHandler函数代码见下:

function doHandler(self, handler) {
    setTimeout(() => {
        const { onReject, onResolve, promise } = handler;
        const { _status, _value } = self;
        const handlerFun = _status === 1 ? onResolve : onReject;
        if (handlerFun === null) {
            _status === 1 ? promise.resolve(_value) : promise.reject(_value);
            return;
        }
        const ret = safeCallOne(handlerFun, _value);
        if (ret === IS_ERROR) {
            promise.reject(ERROR);
            return;
        }
        promise.resolve(ret);
    });
}

我们知道,即使是同步执行relove或者reject,then函数接受的回调函数也不会立刻同步执行,如下代码会依次输出1,3,2,而非1,2,3

const p = new Promise(resolve => {
    console.log(1);
    resolve();
});
p.then(() => {
    console.log(2);
});
console.log(3);

在这里,我使用了setTimeout来模拟这种模式,当然,这只是一种粗糙的模拟,更好的方式是引入或实现类似asap的库(下个星期我可能会实现这个,哈哈),但setTimeout也足够通过测试了

doHandler函数中,我们调用相应的回调函数,需要注意的是,如果相应回调函数为null(null是前文判断回调函数不为function时统一赋值的),则直接调用then函数返回的promise的resolve或reject方法。

同样,我们使用了safeCallOne来捕获错误,这里不再赘述

到这里,我们执行测试,发现不出意外地没有通过,因为我们只是实现了基础的Promise,还没有实现resolve中的thenable功能,下面是mdn对于thenable的描述:

返回一个状态由给定value决定的Promise对象。如果该值是thenable(即,带有then方法的对象),返回的Promise对象的最终状态由then方法执行决定;否则的话(该value为空,基本类型或者不带then方法的对象),返回的Promise对象状态为fulfilled,并且将该value传递给对应的then方法。通常而言,如果你不知道一个值是否是Promise对象,使用Promise.resolve(value) 来返回一个Promise对象,这样就能将该value以Promise对象形式使用

我们再来修改resolve方法:

_Promise.prototype.resolve = function(value) {
    if (this._status !== 0) {
        return;
    }
    if (this === value) {
        return this.reject(new TypeError("cant"s resolve itself"));
    }
    if (value && (typeof value === "function" || typeof value === "object")) {
        const then = getThen(value);
        if (then === IS_ERROR) {
            this.reject(ERROR);
            return;
        }
        if (value instanceof _Promise) {
            value.then(
                value => {
                    this.resolve(value);
                },
                reason => {
                    this.reject(reason);
                }
            );
            return;
        }
        if (typeof then === "function") {
            doFn(this, then.bind(value));
            return;
        }
    }
    this._status = 1;
    this._value = value;
    doThen(this);
};

先判断this和value是否为一个Promise,若是一个,则抛出错误

再判断value的类型是否为function或object,如果是,则实行getThen方法进行错误捕获:

function getThen(self) {
    try {
        return self.then;
    } catch (error) {
        ERROR = error;
        return IS_ERROR;
    }
}

若成功拿到then方法,检测value instanceof _Promise,若为true,则直接采用value的状态和value或者reason。

若then为function,则将then函数以value为this值,当作fn执行,也就是达成下面代码的效果:

const p = new Promise(resolve => {
    resolve({
        then: _resolve => {
            _resolve(1);
        }
    });
});
p.then(value => console.log(value)); //打印1

我们再次执行测试,发现仍然有错,其因出现在下面这种情况下:

const p = new _Promise(resolve => {
    resolve({
        then: _resolve => {
            setTimeout(() => _resolve(1)), 500;
        }
    });
    resolve(2);
});
p.then(value => console.log(value));

这个时候,使用我们的Promise,输出的是2,而在规范中,应当是输出1

原因是我们在对象的then方法中是异步地resolve,这个时候,下面的resolve(2)在执行时,status还没有变,自然可以修改status和value

解决方法也很简单,只用在doFn方法中判断是否为第一次执行即可:

function doFn(self, fn) {
    let done = false;
    const ret = safeCallTwo(
        fn,
        function(value) {
            if (done) {
                return;
            }
            done = true;
            self.resolve(value);
        },
        function(reason) {
            if (done) {
                return;
            }
            done = true;
            self.reject(reason);
        }
    );
    if (ret === IS_ERROR) {
        if (done) {
            return;
        }
        done = true;
        self.reject(ERROR);
    }
}

再执行测试,发现已经测试用例全部通过~

代码

完整代码已放在我的github上,地址为https://github.com/Bowen7/playground/tree/master/promise-polyfill ,可以clone我的playground项目,再到promise-polyfill目录下npm install,然后执行npm test即可运行测试

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

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

相关文章

  • 手写一款符合Promise/A+规范Promise

    摘要:手写一款符合规范的长篇预警有点长,可以选择性观看。初始状态是,状态可以有或者不能从转换为或者从转换成即只要由状态转换为其他状态后,状态就不可变更。 手写一款符合Promise/A+规范的Promise 长篇预警!有点长,可以选择性观看。如果对Promise源码不是很清楚,还是推荐从头看,相信你认真从头看到尾,并且去实际操作了,肯定会有收获的。主要是代码部分有点多,不过好多都是重复的,不...

    rubyshen 评论0 收藏0
  • 只会用就out了,手写一个符合规范Promise

    摘要:传入的回调函数也不是一个函数类型,那怎么办规范中说忽略它就好了。因此需要判断一下回调函数的类型,如果明确是个函数再执行它。 Promise是什么 所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处...

    muzhuyu 评论0 收藏0
  • promise/A+规范翻译以及手写实现

    摘要:如果实现满足所有要求,则实现可能允许。本条款允许使用特定于实现的方法来采用已知一致承诺的状态。接下来根据规范进行手写实现注释偷懒就将对应的规范标注出来,其实基本上就是对着规范实现。 如果要手写实现promise,那么先看看promise/A+规范,再来实现,将会事半功倍。那么我先翻译一下Promise/A+规范中的内容。 术语 1.1 promise 是一个带有符合此规范的the...

    LiuZh 评论0 收藏0
  • 手写一个符合promise/A+规范promise

    摘要:使用及原理分析通过关键字创建实例接受一个参数方法返回两个方法可用通过在方法中通过调用使成功或调用使失败来控制状态中可以执行同步代码也可以执行异步代码原型对象上有方法供实例调用方法接受两个参数默认为一个函数默认为一个函数当状态为时执行用户传入 promise使用及原理分析: 通过new关键字创建promise实例, 接受一个executor参数, executor方法返回两个方法 res...

    venmos 评论0 收藏0
  • JavaScript之手写Promise

    摘要:如果状态是等待态的话,就往回调函数中函数,比如如下代码就会进入等待态的逻辑以上就是简单版实现实现一个符合规范的接下来大部分代码都是根据规范去实现的。 为更好的理解, 推荐阅读Promise/A+ 规范 实现一个简易版 Promise 在完成符合 Promise/A+ 规范的代码之前,我们可以先来实现一个简易版 Promise,因为在面试中,如果你能实现出一个简易版的 Promise ...

    stefan 评论0 收藏0

发表评论

0条评论

jsummer

|高级讲师

TA的文章

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