资讯专栏INFORMATION COLUMN

promise/A+规范翻译以及手写实现

LiuZh / 576人阅读

摘要:如果实现满足所有要求,则实现可能允许。本条款允许使用特定于实现的方法来采用已知一致承诺的状态。接下来根据规范进行手写实现注释偷懒就将对应的规范标注出来,其实基本上就是对着规范实现。

如果要手写实现promise,那么先看看promise/A+规范,再来实现,将会事半功倍。
那么我先翻译一下Promise/A+规范中的内容。

术语
1.1 promise 是一个带有符合此规范的then方法的对象或者函数。
1.2 thenable 是一个定义了一个then方法的对象或者函数。
1.3 value 是一个任意合法的JavaScript值(包括undefined, thenable,或者promise)。
1.4 exception 是一个使用throw语句抛出的值。
1.5 reason 是一个指出为什么promise被rejected的值。

要求
2.1 promise 状态

一个promise必须是三种状态其中的一种状态:pending,fulfilled 或者 rejected。

2.1.1 当promise处于pending状态时:

   2.1.1.1 可以转变到 fulfilled 或者 rejected 状态。

2.1.2 当promise处于fulfilled状态时:

   2.1.2.1 一定不能够转变到其他任何一种状态。
   2.1.2.2 必须有一个value,并且这个值一定不能改变。

2.1.3 当promise处于rejected状态时:

   2.1.3.1 一定不能够转变到其他任何一种状态。
   2.1.3.2 必须有一个reason,并且这个值不能够改变。
   

在这里,“一定不能改变”意味着不可变的身份(即===),但并不意味着深层不变性。(个人理解为是value/reason指向的地址是不可变的,但假若value/reason为一个对象,则对象内的值是可变的。)

2.2 then 方法
一个promise必须提供一个then方法去访问当前或者最终的value或者reason。
一个promise的then方法接受两个参数:

promise.then(onFulfilled, onRejected)

2.2.1 onFulfilled 和 onRejected 都是可选的参数:

    2.2.1.1 如果 onFulfilled  不是一个函数,它必须被忽略。
    2.2.1.2 如果 onRejected 不是一个函数,它必须被忽略。

2.2.2 如果 onFulfilled 是一个函数:

   2.2.2.1 promise 是 fulfilled 状态时它必须被调用,并且 promise 的 value 作为它的第一个参数。
   2.2.2.2 它一定不能在 promise 进入 fulfilled 状态之前被调用。
   2.2.2.3 它一定不能够调用超过一次。

2.2.3 如果 onRejected 时一个函数:

   2.2.3.1 promise 是 rejected 状态时它必须被调用,并且 promise 的 reason 作为它的第一个参数。
   2.2.3.2 它一定不能在 promise 进入 rejected 状态之前被调用。
   2.2.3.3 它一定不能够调用超过一次。

2.2.4 直到执行上下文堆栈仅包含平台代码之前,onFulfilled 或 onRejected 不能够被调用。[3.1]
2.2.5 onFulfilled 和 onRejected 必须以函数的形式被调用(即没有this值)。[3.2]
2.2.6 then 可以在同一个promise被调用多次:

   2.2.6.1 当 promise 处于 fulfilled 状态时,各个 onFulfilled 回调必须按其原始调用的顺序执行。
   2.2.6.2 当 promise 处于 rejected 状态时,各个 onRejected 回调必须按其原始调用的顺序执行。

2.2.7 then 必须返回一个promise [3.3]:

 promise2 = promise1.then(onFulfilled, onRejected);
   2.2.7.1 如果 onFulfilled 或 onRejected 返回一个值 x,运行Promise解决程序 [[Resolve]](promise2, x)。
   2.2.7.2 如果 onFulfilled 或 onRejected 抛出一个意外 e,promise2 必须以 e 为 reason 被 rejected。
   2.2.7.3 如果 onFulfilled 不是一个函数并且 promise1 处于 fulfilled 状态,promise2 必须以与 promise1 同样的 value
           转变到 fulfilled 状态。
   2.2.7.4 如果 onRejected 不是一个函数并且  promise1 处于 rejected状态,promise2 必须以与 promise1 同样的 reason
           转变到 rejected状态。

2.3 Promise解决程序

promise解决程序是一个抽象的操作,它把一个 promise 和一个 value 作为输入,我们将这个表示为 [[Resolve]](promise, x)。如果 x 是一个 thenable ,它将会试图让 promise 采用 x 的状态,前提是x的行为至少有点像一个 promise。否则,它将会用值 x 执行 promise。
对这些 thenable 的处理使得与 promise 实现方式能够去互相操作。只要它们公开了符合 Promise/A+ 的 then 方法。它还使得 promises/A+ 实现方式能够采用合理的 then 方法去“同化”不一致的实现方式。
为了运行[[Resolve]](promise, x),执行以下步骤:
2.3.1 如果 promise 与 x 是同一个对象,以 Tyeperror 作为 reason 去 reject promise。
2.3.2 如果 x 是一个 promise,使用它的状态:

   2.3.2.1 如果 x 处于 pending 状态,promise 必须保持 pending 状态直到 x 处于 fulfilled 或者 rejected 状态。
   2.3.2.2 如果 x 处于 fulfilled 状态,以相同的 value 去 fulfill promise。
   2.3.2.3 如果 x 处于 rejected 状态,以相同的 reason 去 reject promise。

2.3.3 否则,如果 x 是一个对象或者函数:

   2.3.3.1 让 then 作为 x.then。
   2.3.3.2 如果取属性 x.then 会导致抛出异常 e,则以 e 为 reason reject promise。
   2.3.3.3 如果 then 是一个函数,让 x 作为 this 调用它,第一个参数为 resolvePromise,第二个参数为 rejectPromise,然后:
       2.3.3.3.1 如果使用value y 调用 resolvepromise 时,运行[[Resolve]](promise, y)。
       2.3.3.3.2 如果使用reason r 调用 rejectPromise 时,也用 r reject promise。
       2.3.3.3.3 如果 resolvePromise 和 rejectPromise 都被调用了,或多次调用同一参数,那么第一个调用优先,其他的调用都会被忽略。
       2.3.3.3.4 如果调用 then 的过程中抛出了一个意外 e,
           2.3.3.3.4.1 如果 resolvePromise 或者 rejectPromise 被调用了,那么忽略它。
           2.3.3.3.4.2 否则,把 e 作为 reason reject promise。
   2.3.3.4 如果 then 不是一个函数,将 x 作为参数执行 promise。

2.3.4 如果 x 不是一个对象或者函数,将 x 作为参数执行 promise。

如果一个参与了 thenable 循环链的 thenable 去 resolve promise,这样 [[Resolve]](promise, thenable) 的递归性质最终会导致 [[Resolve]](promise, thenable) 会被再次调用,遵循上述算法将会导致无限递归。我们鼓励去实现(但不是必需的)检测这样的递归,并以 TypeError 作为 reason 去 reject Promise。[3.6]

注意
3.1 这里的“平台代码”指的是引擎,环境和 promise 实现代码。实际上,这个要求保证了 onFulfilled 和 onRejected 将会异步执行,在事件循环之后,用一个新的堆栈来调用它。 这可以通过“宏任务”机制(如 settimeou t或 setimmediate )或“微任务”机制(如 mutationobserver 或 process.nextick)来实现。由于 Promise 实现被视为平台代码,因此它本身可能包含一个任务调度队列或“trampoline”,并在其中调用处理程序。
3.2 也就是说,在 strict 模式下,这(指的是this)在它们内部将会是 undefined;在 sloppy 模式下,它将会是全局对象。
3.3 如果实现满足所有要求,则实现可能允许 promise2 == promise1。每个实现都应该记录它是否能够生成 promise2 == promise1 以及在什么条件下。
3.4 一般来说,只有当 X 来自当前的实现时,才知道它是一个真正的 promise。本条款允许使用特定于实现的方法来采用已知一致承诺的状态。
3.5 此过程首先存储对 x 的引用,然后测试该引用,然后调用该引用,避免多次访问 x.then 属性。这些预防措施对于确保访问器属性的一致性非常重要,访问器属性的值可能在两次检索之间发生更改。
3.6 实现方式中不应当在 thenbale 链中的深度设置主观的限制,并且不应当假设链的深度超过主观的限制后会是无限的。只有真正的循环才能导致 TypeError。如果遇到由无限多个不同 thenable 组成的链,那么永远递归是正确的行为。

接下来根据规范进行手写实现,注释偷懒就将对应的规范标注出来,其实基本上就是对着规范实现。

function promise(fuc){ //接收一个函数作为参数
    let that = this;
    that.value = null; // 2.1.2.2
    that.reason = null;// 2.1.3.2
    that.onFulfilled = []; // 2.2.6
    that.onRejected = []; //2.2.6
    that.status = "pending"; // 2.1

    function resolve(val){
        if (that.status === "pending")
        {
            // 2.1.2
            that.status = "fulfilled";
            that.value = val; 
            that.onFulfilled.forEach(fc => fc(val)); //2.2.6.1
        }
    }
    function reject(err){
        if (that.status === "pending")
        {
            //2.1.3
            that.status = "rejected";
            that.reason = err;
            that.onRejected.forEach(fc => fc(err)); //2.2.6.2
        }
    }

    try {
        fuc(resolve,reject);
    } catch (error) {
        reject(error);
    }
}

promise.prototype.then = function (onFulfilled, onRejected) //2.2
{
    let that = this,
        promise2; //2.2.7

    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (x) => x; //2.2.1 2.2.7.3
    onRejected = typeof onRejected === "function" ? onRejected : (e) => { throw e }; // 2.2.7.4

    switch (that.status) {
        case "pending":
            promise2 = new promise((reslove, reject)=>{
                that.onFulfilled.push(()=>{
                    setTimeout(() => {
                        try {
                            let x = onFulfilled(that.value); //2.2.2.1
                            ResolutionProcedure(promise2, x, reslove, reject); //2.2.7.1
                        } catch (err) {
                            reject(err); //2.2.7.2
                        }    
                    }, 0);
                });
                that.onRejected.push(()=>{
                    setTimeout(() => {
                        try {
                            let x = onRejected(that.reason); //2.2.3.1
                            ResolutionProcedure(promise2, x, reslove, reject); //2.2.7.1
                        } catch (err) {
                            reject(err); //2.2.7.2
                        }   
                    }, 0);
                });
            })
            break;
        case "fulfilled":
            promise2 = new promise((reslove,reject)=>{
                setTimeout(() => { // 2.2.4
                    try{
                        let x = onFulfilled(that.value); //2.2.2.1
                        ResolutionProcedure(promise2, x, reslove, reject); //2.2.7.1
                    }catch(err){
                        reject(err); //2.2.7.2
                    }    
                }, 0);
            })
            break;
        case "rejected":
            promise2 = new promise((reslove, reject)=>{
                setTimeout(() => { // 2.2.4
                    try{
                        let x = onRejected(that.reason); //2.2.3.1
                        ResolutionProcedure(promise2, x, reslove, reject); //2.2.7.1
                    }catch(err){
                        reject(err); //2.2.7.2
                    }    
                }, 0);
            })
            break;
        default:
            break;
    }

    return promise2;
}

function ResolutionProcedure(promise, x, reslove, reject){ //2.3
    if (promise === x) //2.3.1
        return reject(new TypeError("same promise"));
    if (x instanceof Promise) //2.3.2
        x.then(((xvalue)=>{
            ResolutionProcedure(promise, xvalue, reslove, reject);
        },(xreason)=>{
            reject(xreason)
        }));
    if (x !== null &&( typeof x === "object" || typeof x === "function" )) //2.3.3
    {
        try {
            let then = x.then;//2.3.3.1
            if(typeof then === "function") //2.3.3.3
            {
                let isuse = false;
                try {
                    then.call(x,(y)=>{
                        if (!isuse)
                        {   
                            ResolutionProcedure(promise, y, reslove, reject); //2.3.3.3.1
                            isuse = true;                        
                        }
                    },(r)=>{
                        if (!isuse) {
                            reject(r) //2.3.3.3.2
                            isuse = true;
                        }
                    })    
                } catch (error) {
                    if(!isuse)
                        reject(error) //2.3.3.3.4.2
                }
            }else{
                reslove(x); //2.3.3.4
            }
        } catch (error) {
            reject(error); //2.3.3.2
        }
    }else{
        reslove(x); //2.3.4
    }
}

做完之后可以通过 promise test 进行测试。
先在代码下面加上一些测试需要代码。

promise.deferred = function () {
    let def = {};
    def.promise = new promise(function (resolve, reject) {
        def.resolve = resolve;
        def.reject = reject;
    });
    return def;
}
module.exports = promise

然后跑一下下面的代码即可。

npm install -g promises-aplus-tests 

promises-aplus-tests  promise.js

结果全部通过,说明符合规范:

参考:链接实现一个完美符合Promise/A+规范的Promise

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

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

相关文章

  • JavaScript 异步

    摘要:从最开始的到封装后的都在试图解决异步编程过程中的问题。为了让编程更美好,我们就需要引入来降低异步编程的复杂性。写一个符合规范并可配合使用的写一个符合规范并可配合使用的理解的工作原理采用回调函数来处理异步编程。 JavaScript怎么使用循环代替(异步)递归 问题描述 在开发过程中,遇到一个需求:在系统初始化时通过http获取一个第三方服务器端的列表,第三方服务器提供了一个接口,可通过...

    tuniutech 评论0 收藏0
  • ES6-7

    摘要:的翻译文档由的维护很多人说,阮老师已经有一本关于的书了入门,觉得看看这本书就足够了。前端的异步解决方案之和异步编程模式在前端开发过程中,显得越来越重要。为了让编程更美好,我们就需要引入来降低异步编程的复杂性。 JavaScript Promise 迷你书(中文版) 超详细介绍promise的gitbook,看完再不会promise...... 本书的目的是以目前还在制定中的ECMASc...

    mudiyouyou 评论0 收藏0
  • 手写一款符合Promise/A+规范Promise

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

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

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

    stefan 评论0 收藏0
  • 手写一个符合A+规范Promise

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

    jsummer 评论0 收藏0

发表评论

0条评论

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