探索Promise/A+规范、解析流程和手写实现

一、前言

Promise现在已经被使用在日常需求的各个异步场景中,本篇文章主要通过探索其规范和解析流程,以手写实现的形式进一步理解Promise的工作原理。

二、基本术语

  • Promise:表示一个拥有then方法的对象或者函数,其行为符合规范

  • thenable:表示定义在then方法上的对象或者函数

  • value:表示任何合法JavaScript值(包括undefined,thenable,或者promise)

  • reason:表示一个表明promise为什么失败的值

  • exception:表示使用throw抛出的值

三、规范

1. Promise状态

  • pending:

    • 只可变为fulfilled或者rejected之一,不可逆
  • fulfilled:

    • 状态不可再改变

    • 必须有一个value,且不可变

  • rejected:

    • 状态不可再改变

    • 必须有一个reason(拒绝的原因),且不可变

2. then方法

首先我们要明确的是,一个promise必须提供一个then方法,该方法可以支持访问其状态转变后的value和reason,其接收两个参数promise.then(onFulfilled, onRejected)

2.1 onFulfilled和onRejected都是可选的参数

  • 2.1.1 如果onFulfilled不是函数,必须被忽略

  • 2.1.2 如果onRejected 不是函数,必须被忽略

2.2 如果onFulfilled是一个函数

  • 2.2.1 它必须在promise由pending转变为fulfilled状态后被调用,然后使用promise的值作为它的第一个参数

  • 2.2.2 它不能在promise转变为fulfilled前被调用

  • 2.2.3 它最多只能被调用1次

2.3 如果onRejected是一个函数

  • 2.3.1 它必须在promise由pending转变为rejected状态后被调用,然后使用promise的值作为它的第一个参数

  • 2.3.2 它不能在promise转变为rejected前被调用

  • 2.3.3 它最多只能被调用1次

2.4 onFulfilled 和 onRejected 只允许在execution context 栈仅包含平台代码时运行

2.5 onFulfilled 和 onRejected 必须被当做函数调用 (i.e. 即函数体内的 this 为undefined)

2.6 对于同一个promise,它的then方法可以被调用多次

  • 2.6.1 当一个promise成功后,所有onFulfilled都必须按照其注册顺序执行

  • 2.6.2 当一个promise失败后,所有onRejected都必须按照其注册顺序执行

2.7 then方法必须返回一个promise

  • 2.7.1 如果onFulfilled或onRejected 返回了值x, 则执行Promise 解析流程[[Resolve]](promise2, x)

  • 2.7.2 如果onFulfilled 或 onRejected抛出了异常e,则promise2应当以e为reason被拒绝。

  • 2.7.3 如果 onFulfilled 不是一个函数且promise1已经完成状态,则promise2必须使用promise1的值完成.

  • 2.7.4 如果 OnReject不是一个函数且promise1已经失败状态, 则promise2必须以相同的reason被拒绝.

四、解析流程

Promise的解析过程其实是以一个输入promise和value的抽象过程,可抽象的表示为[[Resolve]](promise, x)。

其中,如果x是thenable,并且x的行为有点像promise,它会尝试让promise采用x的状态。否则,它将使用x值来完成promise。

解析过程:

  1. 如果promise和x引用同一个对象,则用TypeError作为reason拒绝promise

let promise = Promise.resolve('done').then(() => {

    return promise;

});

promise.then(null, (reason) => {

    console.log('error', reason);

});

// error TypeError: Chaining cycle detected for promise #

  1. 如果x是一个promise,则采用它的状态
  • 2.1 如果x为pending状态,promise必须保持pending状态直到x成功或者失败

let promise = Promise.resolve('done').then(() => {

    return new Promise((resolve, reject) => {

        setTimeout(function () {

            resolve(5000);

        }, 5000);

    });

});

promise.then(

    value => {

        console.log('resolve', value);

    },

    reason => {

        console.log('reject', reason);

    }

);

// resolve 5000

  • 2.2 如果x为完成状态,用相同的值完成promise

let promise = Promise.resolve('done').then(() => {

    return Promise.resolve('success');

});

promise.then(

    value => {

        console.log('resolve', value);

    },

    reason => {

        console.log('reject', reason);

    }

);

// resolve success

  • 2.3 如果x为失败状态,用相同的reason拒绝promise

let promise = Promise.resolve('done').then(() => {

    return Promise.reject('fail');

});

promise.then(

    value => {

        console.log('resolve', value);

    },

    reason => {

        console.log('reject', reason);

    }

);

// reject fail

  1. 如果x是一个对象或函数
  • 3.1 让then为x.then

  • 3.2 如果检索属性x.then的结果抛出一个异常e,用e作为reason拒绝promise

  • 3.3 如果then是一个函数,以x为this调用then函数, 且第一个参数是resolvePromise,第二个参数是rejectPromise,且:

    • 3.3.1 如果resolvePromise被y值调用,运行[[Resolve]](promise, y)

    • 3.3.2 如果rejectPromise被为r的reason调用,用r来拒绝promise

    • 3.3.3 如果 resolvePromise和rejectPromise都被调用,或者对同一个参数进行多次调用,则第一次调用优先,任何进一步的调用被忽略

    • 3.3.4 如果then不是函数,用x来完成promise

  1. 如果x不是对象或者函数,用x来完成promise

此处测试例子较为复杂参考github:点击查看900多行测试例子

五、手写实现一个简单Promise

上述我们对Promise的规范以及解析流程进行了梳理,接下来我们一起来实现一个简单的Promise,我们尚且命名为Promise_以区分Promise。

1. exector函数

exector函数是在创建一个Promise实例时会被立即执行,其接收两个参数resolve和reject


class Promise_ {

    constructor(exector) {

        this.status = 'pending'; // 初始状态

        this.value = undefined; // 初始结果

        this.onResolvedCallbacks = []; // 存储了所有fullfilled回调

        this.onRejectedCallbacks = []; // 存储了所有rejected回调

        let change = (status, value) => { // resolve和reject逻辑统一封装

            if (this.status !== 'pending') return; // 只允许状态为pending向fullfilled和rejected转变

            this.status = status;

            this.value = value;

            let fnArr = status === 'resolved' ? this.onResolvedCallbacks : this.onRejectedCallbacks;

            fnArr.forEach(fn => { // then方法执行链

                if (typeof fn !== 'function') return;

                fn(this.value);

            })

        }

        let resolve = result => { // 成功状态执行

            if (this.onResolvedCallbacks.length > 0) { // 如果当前成功回调为空,使用setTimeout将本次change事件分发为宏任务

                change("resolved", result);

            }

            let timer = setTimeout(() => { // 利用事件循环机制,先进行then方法成功和失败事件注册,以待下一次宏任务时实现链式调用

                change("resolved", result);

                clearTimeout(timer);

            }, 0)

        }

        let reject = reason => { // 失败状态执行

            if (this.onRejectedCallbacks.length > 0) {

                change("rejected", reason);

            }

            let timer = setTimeout(() => { // 同上

                change("rejected", reason);

                clearTimeout(timer);

            }, 0)

        }

        try {

            exector(resolve, reject);

        } catch (err) { // 异常都会导致本次状态变为rejected

            reject(err);

        }

    }

}

通过一个测试案例验证一下状态变更逻辑


let resolvePromise = new Promise_((resolve, reject) => {

    resolve('success');

})

let rejectPromise = new Promise_((resolve, reject) => {

    reject('fail');

})

console.log(resolvePromise, rejectPromise);

0c55cbe716eec6f49967ec0b1676e2a.png

2. then方法

then方法是我们的关键方法,其主要有两个参数,resolve为成功时的回调,reject为失败时的回调。

then方法返回的是一个新的Promise实例,以继续支持then的链式调用,且当resolve和reject不是一个function时,能够继续执行then链,并将value或者reason进行传递。


class Promise_ {

    constructor(exector) {

        // ...

    }



    then(resolveCallback, rejectCallback) {

        if (typeof resolveCallback !== 'function') { // 如果成功回调不是一个方法,传递它的值

            resolveCallback = result => {

                return Promise_.resolve(result);

            }

        }

        if (typeof rejectCallback !== 'function') { // 同上

            rejectCallback = reason => {

                return Promise_.reject(reason);

            }

        }

        return new Promise_((resolve, reject) => { // then返回的是一个新的promise,其状态为pending

            this.onResolvedCallbacks.push(result => { // 此处为箭头函数,this指向上一个promise,往上面一个成功回调添加事件

                try {

                    let x = resolveCallback(result);

                    if (x instanceof Promise_) { // 如果x是一个promise实例,继续执行then

                        x.then(resolve, reject);

                        return;

                    }

                    resolve(x);

                } catch (err) {

                    reject(err);

                }

            })

            this.onRejectedCallbacks.push(reason => { // 同上

                try {

                    let x = rejectCallback(reason);

                    if (x instanceof Promise_) {

                        x.then(resolve, reject);

                        return;

                    }

                    resolve(x); // 注意此处!!!上一个promise的reject并不会影响到下一个promise的resolve

                } catch (err) {

                    reject(err);

                }

            })

        })

    }

}

编写一个简单例子测试上述功能


new Promise_((resolve, reject) => {

    resolve('success')

    reject('fail')

}).then(

    result => { console.log('resolve', result) },

    reason => { console.log('reject', reason) }

)

new Promise_((resolve, reject) => {

    reject('fail')

    resolve('success')

}).then(

    result => { console.log('resolve', result) },

    reason => { console.log('reject', reason) }

)

new Promise_((resolve, reject) => {

    reject('fail')

    resolve('success')

}).then(

    result => { console.log('resolve', result); },

    reason => reason

).then(

    result => { console.log('resolve', result); },

    reason => { console.log('reject', reason) }

)

结果如下:

d6867b3d4fe44e5144bdb3075230005.png

3. resolve方法

Promise还支持以Promise.resolve(value)的形式,并且其返回的是一个Promise,支持then链式调用


static resolve(result) {

    return new Promise_(resolve => {

        resolve(result);

    })

}

测试代码


Promise_

    .resolve('done')

    .then(

        result => { console.log('resolve', result); },

        reason => { console.log('reject', reason) }

    )

71445bc10b2513179158801cdbaae88.png

4. reject方法

该方法原理同resolve方法


static reject(reason) {

    return new Promise_((resolve, reject) => {

        reject(reason);

    })

}

5. catch方法

catch方法能够捕获到异常代码,并且返回的是一个Promise,通过then方法的第二个参数进行reject


catch(rejectCallback) {

    return this.then(null, rejectCallback);

}

简单测试案例:


Promise_

    .reject('fail')

    .catch(err => {

        console.log('err:', err)

    })

Promise_

    .resolve('success')

    .then(result => {

        throw new Error('fail')

    })

    .catch(err => {

        console.log('err:', err)

    })

结果:

bdb50c82f73d7a2e03c997d83d537bc.png

6. finally方法

finally方法是在成功和失败的场景下都会执行的方法


finally(finallyCallback) {

    return this.then(

        value => this.constructor.resolve(finallyCallback()).then(() => value),

        reason => this.constructor.reject(finallyCallback()).then(() => reason)

    )

}

测试功能:


Promise_

    .resolve('done')

    .then(res => {

        console.log('resolve', res)

    })

    .finally(() => {

        console.log('finnally')

    })

8ba57a1f7e2c7335c25df5729e7b174.png

7. all方法

all方法传入一个promise array参数,其规则为只有当所有的promise都状态为完成时,才会返回一个结果数组


static all(promiseList) {

    return new Promise_((resolve, reject) => {

        let index = 0,

            results = [],

            promiseListLen = promiseList.length;

        for (let i = 0; i < promiseListLen; i++) {

            let promise = promiseList[i];

            if (!(promise instanceof Promise_)) return;

            promise

                .then(result => {

                    index++;

                    results[i] = result;

                    if (index === promiseListLen) resolve(results);

                })

                .catch(reason => {

                    reject(reason);

                })

        }

    })

}

编写一个简单测试案例:


let promiseList = [

    Promise_.resolve(1),

    new Promise_((resolve, reject) => {

        setTimeout(() => {

            resolve(2)

        }, 5000)

    })

]

Promise_

    .all(promiseList)

    .then(

        result => { console.log('resolve', result); },

        reason => { console.log('reject', reason) }

    )

等待超过5秒钟以后得到结果:

f7838f7e03b2028efb9d08ef474b921.png

8. race方法

race方法的参数和all方法一致,不同在于race方法是返回第一个状态为完成的promise的结果


static race(promiseList) {

    return new Promise_((resolve, reject) => {

        for (let i = 0; i < promiseList.length; i++) {

            let promise = promiseList[i];

            if (!(promise instanceof Promise_)) return;

            promise

                .then(result => {

                    resolve(result)

                })

                .catch(err => {

                    reject(err)

                })

        }

    })

}

简单测试代码:


let promiseList = [

    new Promise_((resolve, reject) => {

        setTimeout(() => {

            resolve(1)

        }, 5000)

    }),

    new Promise_((resolve, reject) => {

        setTimeout(() => {

            resolve(2)

        }, 1000)

    })

]

Promise_

    .race(promiseList)

    .then(

        result => { console.log('resolve', result); },

        reason => { console.log('reject', reason) }

    )

586da169903ceaec03f008876930ccb.png

以上通过对Promise的规范和解析流程进行了分析,通过实现部分Promise的api帮助我们进一步理解Promise的工作原理。当然了,这个Promise_肯定不是完美的,实际上Promise内部自己实现了一个事件循环,并且在各端表现上事件循环还有一些差异。

六、参考文献

  1. Promise/A+规范 https://promisesaplus.com/

  2. ECMAScript 6 https://es6.ruanyifeng.com/#docs/promise#Promise-all

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,463评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,868评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,213评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,666评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,759评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,725评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,716评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,484评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,928评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,233评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,393评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,073评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,718评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,308评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,538评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,338评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,260评论 2 352