1

手写Promise

按照Promise/A+规范,手写一个Promise

Promise的相关性质在这里就不做赘述,直接开搞!

Promise的基本框架搭建

Promise代码基本结构

实例化Promise对象时传入一个函数作为执行器executor,其有两个参数(resolvereject)分别将结果变为成功态和失败态。其中包含3个属性,分别为statevaluereason

state:保存Promise对象的状态,可以为:等待状态 pending、成功状态resolved、失败状态rejected

并且要求Promise在实例化后立即执行,因此,基本结构可以写成:

function MyPromise(executor) {
    var _this = this;
    this.state = 'pending';
    this.value = undefined;
    this.reason = undefined;
    
    function resolve(value){
        //some code
    }
    
    function reject(reason){
        //some code
    }
    //这样的话,实例化一个MyPromise对象时,直接运行executor函数
    executor(resolve,reject)
}

then方法定义在原型上

每一个Promise实例都有一个then方法,用来处理异步返回的结果,因此将then方法定义在原型上。

MyPromise.prototype.then = function(onFulfilled,onRejected){
    //some code
}

此时,Promise的基本框架已经搭好,接下来对其功能进行完善。

实现Promise的状态改变

我们知道resolve函数在异步操作成功时,将Promise对象的状态从“未完成”变为“成功”,并将异步操作的结果作为参数传递出去。reject函数:在异步操作失败时将Promise对象的状态从“未完成”变为“失败”,并将异步操作报出的错误作为参数传递出去。并且状态的改变只能是pending->resolvedpending->rejected,以及变化后状态固定。

function resolve(value){
    //当状态为pending时才可以改变状态
    if(_this.state === 'pending'){
        _this.value = value;
        _this.state = 'resolved'
    }
}

function reject(reason){
    //当状态为pending时才可以改变状态
    if(_this.state === 'pending'){
        _this.reason = reason;
        _this.state = 'rejected'
    }
}

基本实现then方法

因为当Promise状态发生改变后,都会调用then方法,不论成功或失败。这个函数的参数都是可选的,当参数不为函数则忽略。如果传入的参数为函数则执行。

MyPromise.prototype.then = function(onFulfilled,onRejected){
    if(this.state === 'resolved'){
        if(typeof onFulfilled === 'function'){
            onFulfilled(this.value)
        }
    }
    if(this.state === 'rejected'){
        if(typeof onRejected === 'function'){
            onRejected(this.reason)
        }
    }
}

完善Promise的功能

目前为止,已经可以实现Promise的基本功能,但仍有某些性质需要完善,比如异步运行,链式调用,返回新的Promise等,接下来便是对Promise的逐步完善。

实现异步执行

现在的MyPromise还不支持异步代码。什么意思呢?来举例说明

let p1 = new MyPromise((resolve,reject) => {
    resolve(1)
});
p1.then(data => console.log(data))

let p2 = new MyPromise((resolve,reject) => {
    setTimeout(() => {
        resolve(1)
    },0)
});
p2.then(data => console.log(data))  //没有输出

由于代码的执行顺序是先执行同步代码再执行异步代码,then()方法在state属性改变前就已执行,因此上方代码中的p2没有输出。

鉴于这个原因,我们参考发布订阅者模式。MyPromise的state作为发布者,各个onFulfilledonRejected作为订阅者。调用then()时,先将onFulfilledonRejected函数保存起来。当异步操作完成,state状态变化,通知订阅者并调用。

因此,对MyPromise进行改进。

首先,在MyPromise的构造函数中新增两个属性,onFulfilledFunconRejectedFunc分别用来保存onFulfilledonRejected函数,因为可能有多个函数,所以这两个属性均为数组。

function MyPromise(executor) {
    var _this = this;
    this.state = 'pending';
    this.value = undefined;
    this.reason = undefined;
    this.onFulfilledFunc = [];//保存状态变为成功时调用的回调函数
    this.onRejectedFunc = [];//保存状态变为失败时调用的回调函数
    // other code...
}

其次,调用then()时将onFulfilledonRejected函数放入对应的数组中。

MyPromise.prototype.then = function(onFulfilled,onRejected){
    //Promise中有异步代码
    if(this.state === 'pending'){
        if(typeof onFulfilled === 'function'){
            this.onFulfilledFunc.push(onFulfilled)//保存
        }
        if(typeof onRejected === 'function'){
            this.onRejectedFunc.push(onRejected)//保存
        }
    }
    //Promise中为同步代码,立即执行
    if(this.state === 'resolved'){
        if(typeof onFulfilled === 'function'){
            onFulfilled(this.value)
        }
    }
    //Promise中位同步代码,立即执行
    if(this.state === 'rejected'){
        if(typeof onRejected === 'function'){
            onRejected(this.reason)
        }
    }
}

最后,当异步操作完成,改变state状态,并调用对应数组中所有函数。

function MyPromise(executor) {
    // other code...
    function resolve(value){
    //当状态为pending时才可以改变状态
    if(_this.state === 'pending'){
        _this.value = value;
        _this.state = 'resolved';
        _this.onFulfilledFunc.forEach(fn => fn(value))
    }
}

function reject(reason){
    //当状态为pending时才可以改变状态
    if(_this.state === 'pending'){
        _this.reason = reason;
        _this.state = 'rejected';
         _this.onRejectedFunc.forEach(fn => fn(reason))
    }
}
}

此时,已经支持异步操作。

//测试
let p1 = new MyPromise((resolve,reject) => {
    resolve(1)
});
p1.then(data => console.log(data))  //1

let p2 = new MyPromise((resolve,reject) => {
    setTimeout(() => {
        resolve(2)
    },0)
});
p2.then(data => console.log(data))  //2

保证then方法返回一个新的Promise

使用Promise进行异步编程的最重要的原因就是其规避了嵌套地狱,改为了链式调用。要想实现链式调用,那么then()返回一个Promise对象是前提。

规定onFulfilled,onRejected都是可选参数,如果他们不是函数,必须被忽略

  • 若onFulfilled是一个普通的值,成功时直接等于 value => value
  • 若onRejected是一个普通的值,失败时如果直接等于 value => value,则会跑到下一个then中的onFulfilled中,所以直接扔出一个错误reason => throw err
MyPromise.prototype.then = function (onFulfilled, onRejected) {
    // onFulfilled如果不是函数,就忽略onFulfilled,直接返回value
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    // onRejected如果不是函数,就忽略onRejected,直接扔出错误
    onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
    let promise2 = new Promise((resolve, reject) => {
    //some code
    }
    return promise2;
};

实现Promise的链式调用

Promise链式调用的规则为:

  1. 每个then方法都返回一个新的Promise对象
  2. 如果then方法中显式的返回了一个Promise对象,就不做任何变动,直接返回其结果
  3. 如果then方法中返回的是一个普通值(Number、String等)就将这个值包装为一个新Promise对象并返回
  4. 如果then方法中没有return语句,则视为返回一个包装Undefined的Promise对象
  5. 如果then方法中出现异常,则调用失败态方法(reject)跳转到下一个thenonRejected函数
  6. 如果then方法没有传入任何回调,则继续向下传递值

因为要实现的这些功能较为复杂,抽离一个方法来实现。

/**
 * 解析then返回值与新Promise对象
 * @param {Object} promise2 新的Promise对象 
 * @param {*} x 上一个then的返回值
 * @param {Function} resolve promise2的resolve
 * @param {Function} reject promise2的reject
 */
function resolvePromise(promise2, x, resolve, reject) {
    //...
}

首先需要判断参数promise2x是否相等,若相等则会发生循环引用,无法正常执行。

function resolvePromise(promise2, x, resolve, reject){
    if(promise2 == x){
        reject(new TypeError('Promise发生了循环引用'))
    }
}

然后按参数x的类型分别处理。当x是一个Promise,就直接执行;当x是一个对象或函数,再进一步处理;剩下情况则为对普通值的处理。

function resolvePromise(promise2, x, resolve, reject){
    if(promise2 == x){
        reject(new TypeError('Promise发生了循环引用'))
    }
    
    if(x!=null && (typeof x==='object'||typeof x==='function')){
        //x为对象或函数
    }else{
        //x为普通值
        resolve(x)
    }
}

如果x是一个对象,我们就以是否有then方法判断其是否为Promise对象。若报错,则直接将promise2转为失败态。若有then,且为一个函数,就可认定x是Promise对象,之后使用x作为this调用then方法。

function resolvePromise(promise2, x, resolve, reject){
    if(promise2 == x){
        reject(new TypeError('Promise发生了循环引用'))
    }
    
    if(x!=null && (typeof x==='object'||typeof x==='function')){
        //x为对象或函数
        try{
            let then = x.then;
            if(typeof then === 'function'){
                //then是function,那么执行Promise
                then.call(x, y =>{
                    resolve(y)
                },(r) => {
                    reject(r)
                })
            }
        }catch(e){
            reject(e)
        }
    }else{
        //x为普通值
        resolve(x)
    }
}

若Promise对象转为成功态或失败态时传入了一个Promise对象,此时应使用递归的方法继续执行,直至Promise全部执行完。

function resolvePromise(promise2, x, resolve, reject){
    if(promise2 == x){
        reject(new TypeError('Promise发生了循环引用'))
    }
    
    if(x!=null && (typeof x==='object'||typeof x==='function')){
        //x为对象或函数
        try{
            let then = x.then;
            if(typeof then === 'function'){
                //then是function,那么执行Promise
                then.call(x, y =>{
                    //递归调用,传入y若是Promise对象,继续循环
                    resolvePromise(promise2, y, resolve, reject)
                },(r) => {
                    reject(r)
                })
            }
        }catch(e){
            reject(e)
        }
    }else{
        //x为普通值
        resolve(x)
    }
}

最后,由于我们自己的代码是同步执行,那么使用setTimeout函数来变为异步执行。

setTimeout(() => {
    try {
        let x = onFulfilled(value);
        resolvePromise(promise2, x, resolve, reject);
    } catch (e) {
        reject(e);
    }
},0);

完整代码

function MyPromise(executor) {
    let _this = this;
    this.state = 'pending';
    this.value = undefined;
    this.reason = undefined;
    this.onFulfilledFunc = [];//保存状态变为成功时调用的回调函数
    this.onRejectedFunc = [];//保存状态变为失败时调用的回调函数

    function resolve(value){
        //当状态为pending时才可以改变状态
        if(_this.state === 'pending'){
            _this.value = value;
            _this.state = 'resolved';
            _this.onFulfilledFunc.forEach(fn => fn(value))
        }
    }

    function reject(reason){
        //当状态为pending时才可以改变状态
        if(_this.state === 'pending'){
            _this.reason = reason;
            _this.state = 'rejected';
            _this.onRejectedFunc.forEach(fn => fn(reason))
        }
    }
    //这样的话,实例化一个MyPromise对象时,直接运行executor函数
    //executor执行可能出错
    try{
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
}

MyPromise.prototype.then = function(onFulfilled,onRejected){
    // onFulfilled如果不是函数,就忽略onFulfilled,直接返回value
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    // onRejected如果不是函数,就忽略onRejected,直接扔出错误
    onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
    let promise2 = new MyPromise((resolve,reject) => {
        //Promise中有异步代码
        if(this.state === 'pending'){
            this.onFulfilledFunc.push(() => {
                // 异步
                setTimeout(() => {
                    try {
                        let x = onFulfilled(this.value);
                        // resolvePromise函数,处理自己return的promise和默认的promise2的关系
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                }, 0);
            });//保存
            this.onRejectedFunc.push(() => {
                setTimeout(() => {
                    try{

                        let x = onRejected(this.reason);
                        // resolvePromise函数,处理自己return的promise和默认的promise2的关系
                        resolvePromise(promise2, x, resolve, reject)
                    }catch(e){
                        reject(e)
                    }
                },0)
            })//保存
        }

        if(this.state === 'resolved'){
            // 异步
            setTimeout(() => {
                try {
                    let x = onFulfilled(this.value);

                    // resolvePromise函数,处理自己return的promise和默认的promise2的关系
                    resolvePromise(promise2, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            }, 0)
        }

        if(this.state === 'rejected'){
            // 异步
            setTimeout(() => {
                try{
                    let x = onRejected(this.reason);
                    // resolvePromise函数,处理自己return的promise和默认的promise2的关系
                    resolvePromise(promise2, x, resolve, reject)
                }catch(e){
                    reject(e)
                }
            },0)
        }
});
    return promise2
};


/**
 * 解析then返回值与新Promise对象
 * @param {Object} promise2 新的Promise对象 
 * @param {*} x 上一个then的返回值
 * @param {Function} resolve promise2的resolve
 * @param {Function} reject promise2的reject
 */
function resolvePromise(promise2, x, resolve, reject){
    if(promise2 === x){
        reject(new TypeError('Promise发生了循环引用'))
    }

    if(x!=null && (typeof x==='object'||typeof x==='function')){
        //x为对象或函数
        try{
            let then = x.then;
            if(typeof then === 'function'){
                //then是function,那么执行Promise
                then.call(x, y =>{
                    //递归调用,传入y若是Promise对象,继续循环
                    resolvePromise(promise2, y, resolve, reject)
                },(r) => {
                    reject(r)
                })
            }
        }catch(e){
            reject(e)
        }
    }else{
        //x为普通值
        resolve(x)
    }
}

HeXuan
9 声望1 粉丝

引用和评论

0 条评论