Promise的特性及实现原理

Promise对象的特性

要实现Promise对象首先我们要了解Promise拥有哪些特性,简单概括为以下几点
1、Promise有三种状态:pending(进行中)、fulfilled(已成功)、rejected(已失败)
2、Promise对象接受一个回调函数作为参数, 该回调函数接受两个参数,分别是成功时的回调resolve和失败时的回调reject;另外resolve的参数除了正常值以外, 还可能是一个Promise对象的实例;reject的参数通常是一个Error对象的实例。
3、then方法返回一个新的Promise实例,并接收两个参数onResolved(fulfilled状态的回调);
onRejected(rejected状态的回调,该参数可选)
4、catch方法返回一个新的Promise实例
5、finally方法不管Promise状态如何都会执行,该方法的回调函数不接受任何参数
6、Promise.all()方法将多个多个Promise实例,包装成一个新的Promise实例,该方法接受一个由Promise对象
组成的数组作为参数(Promise.all()方法的参数可以不是数组,但必须具有Iterator接口,且返回的每个成员都是Promise实例),注意参数中只要有一个实例触发catch方法,都会触发Promise.all()方法返回的新的实例的catch方法,如果参数中的某个实例本身调用了catch方法,将不会触发Promise.all()方法返回的新实例的catch方法
7、Promise.race()方法的参数与Promise.all方法一样,参数中的实例只要有一个率先改变状态就会将该实例的状态传给Promise.race()方法,并将返回值作为Promise.race()方法产生的Promise实例的返回值
8、Promise.resolve()将现有对象转为Promise对象,如果该方法的参数为一个Promise对象,Promise.resolve()将不做任何处理;如果参数thenable对象(即具有then方法),Promise.resolve()将该对象转为Promise对象并立即执行then方法;如果参数是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的Promise对象,状态为fulfilled,其参数将会作为then方法中onResolved回调函数的参数,如果Promise.resolve方法不带参数,会直接返回一个fulfilled状态的 Promise 对象。需要注意的是,立即resolve()的 Promise 对象,是在本轮“事件循环”(event loop)的结束时执行,而不是在下一轮“事件循环”的开始时。

setTimeout(function () {
  console.log('three');
}, 0);

Promise.resolve().then(function () {
  console.log('two');
});

console.log('one');

// one
// two
// three

9、Promise.reject()同样返回一个新的Promise对象,状态为rejected,无论传入任何参数都将作为reject()的参数
以上就是Promise对象的一些基本特性,下面我们根据Promise对象的特性,自己来实现一个简单的Promise对象

第一步、Promise对象用三种状态分别是:pending、fulfilled、rejected。
resolve的参数为一个Promise对象的实例的时时候该实例的resolve的参数即为外层Promise对象then方法中onResolved方法的参数,reject的参数即为外层Promise对象then方法(或catch方法)中onRejected方法的参数

function timeout2() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            try {
                a // 此处a未定义,如果注释掉这里即正常执行
                resolve(200)
            } catch (e) {
                reject(e)
            }
        }, 1000)
    })
}
function timeout(time) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            try {
                resolve(timeout2())
            } catch (e) {
                reject(e)
            }
        }, time)
    })
}
let p = timeout(1000);
p.then(res => {
    console.log(res); // 200
}).catch(err => {
    console.log(err); // ReferenceError: a is not defined
})

由上面的例子我们可以定义一个基本框架:

/*
    Promise构造函数接收一个executor函数, executor函数执行完同步或异步操作后,调用它的两个参数resolve和reject
    如果操作成功,调用resolve并传入value
    如果操作失败,调用reject并传入reason
*/
class MyPromise {
    constructor(executor) {
        if(typeof executor !== 'function') {
            throw new Error('MyPromise must accept a function as a parameter')
        }
        // Promise当前的状态
        this.status = 'pending'
        // Promise的值
        this.data = undefined
        // Promise resolve时的回调函数集,因为在Promise结束之前有可能有多个回调添加到它上面
        this.onResolvedCallback = []
        // Promise reject时的回调函数集,因为在Promise结束之前有可能有多个回调添加到它上面
        this.onRejectedCallback = []
        /*
            考虑到执行executor的过程中有可能出错,所以我们用try/catch块给包起来,
            并且在出错后以catch到的值reject掉这个Promise,另外因为resolve和reject在外部调用故需要绑定this
        */
        try {
            executor(this.resolve.bind(this), this.reject.bind(this))
        } catch (err) {
            this.reject(err)
        }
    }

    resolve(value) {
        // 成功时将状态改为fulfilled
        if(this.status === 'padding') {
            // 如果传入的值是一个promise实例,则必须等待该Promise对象状态改变后,
            // 当前Promsie的状态才会改变,且状态取决于参数Promsie对象的状态
            if(value instanceof MyPromise) {
                value.then(res => {
                    this.data = res
                    this.status = 'fulfilled'
                    //执行resolve的回调函数,将value传递到callback中
                    this.onResolvedCallback.forEach(callback => callback(res))
                }, err => {
                    this.data = err
                    this.status = 'rejected'
                    //执行reject的回调函数,将reason传递到callback中
                    this.onRejectedCallback.forEach(callback => callback(err))
                })
            } else {
                this.status = 'fulfilled';
                this.data = value;
                //执行resolve的回调函数,将value传递到callback中
                this.onResolvedCallback.forEach(callback => callback(value))
            }
        }
    }
    reject(reason) {
        // 失败时将状态改为rejected
        if(this.status === 'padding') {
            this.status = 'rejected'
            this.data = reason;
            // 触发所有的回调函数
            this.onRejectedCallback.forEach(item => {
                item(reason)
            })
        }
    }

}

第二步、实现then方法
Promise实例的then方法返回一个新的Promise实例,并接收两个参数onResolved(fulfilled状态的回调); onRejected(rejected状态的回调,该参数可选);

// then方法接收两个参数,onResolved,onRejected,分别为Promise成功或失败后的回调
class MyPromise {
    // ....
    then(onResolved, onRejected) {
        // 根据标准,如果then的参数不是function,则我们需要忽略它,此处以如下方式处理
        onResolved = typeof onResolved === 'function' ? onResolved : value => {}
        onRejected = typeof onRejected === 'function' ? onRejected : reason => {}

        if (this.status === 'fulfilled') {
            return new MyPromise((resolve, reject) => {

            })
        }
        if (this.status === 'rejected') {
            return new MyPromise((resolve, reject) => {

            })
        }
        if (this.status === 'pending') {
            return new MyPromise((resolve, reject) => {

            })
        }
    }
}

当我们在链式调用Promise实例的时候,当一个实例的then方法返回另一个实例,第二个then方法指定的回调函数,就会等待这个新的Promise对象状态发生变化。如果变为fulfilled,就调用第一个回调函数(即onResolved),如果状态变为rejected,就调用第二个回调函数(即onRejected)。

let p1 = new Promise((resolve,reject) => {
    setTimeout(function(){
        try {
            resolve(1)
        } catch (e) {
            reject(e)
        }
    }, 100)
})
let p2 = new Promise((resolve,reject) => {
    setTimeout(function(){
        try {
            resolve(2)
        } catch (e) {
            reject(e)
        }
    }, 100)
})
p1.then(res => {
    console.log(res); // 1
    return p2
}).then(res => {
    console.log(res); // 2
}).catch(err => {

})

第三步、完善then方法

根据上面的例子我们来补充then方法里面的内容

// then方法接收两个参数,onResolved,onRejected,分别为Promise成功或失败后的回调
class MyPromise {
    // ....
    then(onResolved, onRejected) {
        // 根据标准,如果then的参数不是function,则我们需要忽略它,此处以如下方式处理
        onResolved = typeof onResolved === 'function' ? onResolved : value => value
        onRejected = typeof onRejected === 'function' ? onRejected : reason => reason

        // 如果promise1(此处即为this)的状态已经确定并且是resolved,我们调用onResolved
        // 因为考虑到有可能throw,所以我们将其包在try/catch块里
        if (this.status === 'fulfilled') {
            return new MyPromise((resolve, reject) => {
                try {
                    let result = onResolved(this.data)
                     // 如果onResolved的返回值是一个Promise对象,直接取它的结果做为返回promise实例的结果
                    if(result instanceof MyPromise) {
                        result.then(resolve, reject)
                    }
                    resolve(result) // 否则,以它的返回值做为返回promise实例的结果
                } catch (e) {
                    reject(e)
                }
            })
        }
        // rejected状态的处理方法与上面基本一致
        if (this.status === 'rejected') {
            return new MyPromise((resolve, reject) => {
                try {
                    let result = onRejected(this.data)
                     // 如果onRejected的返回值是一个Promise对象,直接取它的结果做为返回promise实例的结果
                    if(result instanceof MyPromise) {
                        result.then(resolve, reject)
                    }
                } catch (e) {
                    reject(e)
                }
            })
        }
        if (this.status === 'pending') {
            /**
            * 如果当前的Promise还处于pending状态,我们并不能确定调用onResolved还是onRejected,
            * 只能等到Promise的状态确定后,才能确实如何处理。
            * 所以我们需要把以上两种情况的处理逻辑做为callback放入promise1(此处即this)的回调数组里
            * 具体逻辑也与上面类似
            */
            return new MyPromise((resolve, reject) => {
                this.onResolvedCallback.push((value) => {
                    try {
                        let result = onResolved(this.data);
                        if (result instanceof MyPromise) {
                            result.then(resolve, reject)
                        }
                    } catch (e) {
                        reject(e)
                    }
                })
                this.onRejectedCallback.push(reason => {
                    try {
                        let result = onRejected(this.data)
                        if (result instanceof MyPromise) {
                            result.then(resolve, reject)
                        }
                    } catch (e) {
                        reject(e)
                    }
                })
            })
        }
    }
}

第四步、完成catch方法
以上我们就实现了一个promise对象的基本功能,最后加上catch方法完整代码如下


/*
    Promise构造函数接收一个executor函数, executor函数执行完同步或异步操作后,调用它的两个参数resolve和reject
    如果操作成功,调用resolve并传入value
    如果操作失败,调用reject并传入reason
*/
class MyPromise {
    constructor(executor) {
        if(typeof executor !== 'function') {
            throw new Error('MyPromise must accept a function as a parameter')
        }
        // Promise当前的状态
        this.status = 'pending'
        // Promise的值
        this.data = undefined
        // Promise resolve时的回调函数集,因为在Promise结束之前有可能有多个回调添加到它上面
        this.onResolvedCallback = []
        // Promise reject时的回调函数集,因为在Promise结束之前有可能有多个回调添加到它上面
        this.onRejectedCallback = []
        /*
            考虑到执行executor的过程中有可能出错,所以我们用try/catch块给包起来,
            并且在出错后以catch到的值reject掉这个Promise,另外因为resolve和reject在外部调用故需要绑定this
        */
        try {
            executor(this.resolve.bind(this), this.reject.bind(this))
        } catch (err) {
            this.reject(err)
        }
    }

    resolve(value) {
        // 成功时将状态改为fulfilled
        if(this.status === 'padding') {
            // 如果传入的值是一个promise实例,则必须等待该Promise对象状态改变后,
            // 当前Promsie的状态才会改变,且状态取决于参数Promsie对象的状态
            if(value instanceof MyPromise) {
                value.then(res => {
                    this.data = res
                    this.status = 'fulfilled'
                    //执行resolve的回调函数,将value传递到callback中
                    this.onResolvedCallback.forEach(callback => callback(res))
                }, err => {
                    this.data = err
                    this.status = 'rejected'
                    //执行reject的回调函数,将reason传递到callback中
                    this.onRejectedCallback.forEach(callback => callback(err))
                })
            } else {
                this.status = 'fulfilled';
                this.data = value;
                //执行resolve的回调函数,将value传递到callback中
                this.onResolvedCallback.forEach(callback => callback(value))
            }
        }
    }
    reject(reason) {
        // 失败时将状态改为rejected
        if(this.status === 'padding') {
            this.status = 'rejected'
            this.data = reason;
            // 触发所有的回调函数
            this.onRejectedCallback.forEach(item => {
                item(reason)
            })
        }
    }
    then(onResolved, onRejected) {
        // 根据标准,如果then的参数不是function,则我们需要忽略它,此处以如下方式处理
        onResolved = typeof onResolved === 'function' ? onResolved : value => value
        onRejected = typeof onRejected === 'function' ? onRejected : reason => reason

        // 如果promise1(此处即为this)的状态已经确定并且是resolved,我们调用onResolved
        // 因为考虑到有可能throw,所以我们将其包在try/catch块里
        if (this.status === 'fulfilled') {
            return new MyPromise((resolve, reject) => {
                try {
                    let result = onResolved(this.data)
                     // 如果onResolved的返回值是一个Promise对象,直接取它的结果做为返回promise实例的结果
                    if(result instanceof MyPromise) {
                        result.then(resolve, reject)
                    }
                    resolve(result) // 否则,以它的返回值做为返回promise实例的结果
                } catch (e) {
                    reject(e)
                }
            })
        }
        // rejected状态的处理方法与上面基本一致
        if (this.status === 'rejected') {
            return new MyPromise((resolve, reject) => {
                try {
                    let result = onRejected(this.data)
                     // 如果onRejected的返回值是一个Promise对象,直接取它的结果做为返回promise实例的结果
                    if(result instanceof MyPromise) {
                        result.then(resolve, reject)
                    }
                } catch (e) {
                    reject(e)
                }
            })
        }
        if (this.status === 'pending') {
            /**
            * 如果当前的Promise还处于pending状态,我们并不能确定调用onResolved还是onRejected,
            * 只能等到Promise的状态确定后,才能确实如何处理。
            * 所以我们需要把以上两种情况的处理逻辑做为callback放入promise1(此处即this)的回调数组里
            * 具体逻辑也与上面类似
            */
            return new MyPromise((resolve, reject) => {
                this.onResolvedCallback.push((value) => {
                    try {
                        let result = onResolved(this.data);
                        if (result instanceof MyPromise) {
                            result.then(resolve, reject)
                        }
                    } catch (e) {
                        reject(e)
                    }
                })
                this.onRejectedCallback.push(reason => {
                    try {
                        let result = onRejected(this.data)
                        if (result instanceof MyPromise) {
                            result.then(resolve, reject)
                        }
                    } catch (e) {
                        reject(e)
                    }
                })
            })
        }
    }
    catch(onRejected) {
        return this.then(null, onRejected)
    }
}

以上就实现了Promise对象的核心功能,其他的几个特性很容易实现在此不做赘述,由于本人水平有限,如有错漏之处请指出斧正。

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

推荐阅读更多精彩内容