实现一个promise

256 阅读9分钟

期约基础

ES6新增的引用类型Promise,可以通过new操作符来实例化。 创建新期约时需要传入执行器(executor)函数作为参数。

期约状态机

  • 待定(pending)
  • 期约最初始状态 期约可以落定(settled)为代表成功的兑现(fulfilled)状态,或代表失败的拒绝(rejected)状态,无论哪种状态都是不可逆的

  • 兑现(fulfilled,有时也称“解决”,resolved)
  • 拒绝(rejected)

期约实例方法

期约实例方法是连接外部同步代码与内部异步代码之间的桥梁。这些方法可以访问异步操作返回的数据,处理期约成功和失败的结果,连续对期约求值,或者添加只有期约进入终止状态时才会执行的代码。

实现Thenable接口

在ES暴露的异步结构中,任何对象都有一个then()方法。

Promise.prototype.then()

是为期约实例添加处理程序的主要方法。这个then()方法接受最多的两个参数:onResolved处理程序和onRejected处理程序。这两个参数都是可选的。

function Promise(executor){
    this.status = 'pending';
    this.value = null;
    this.reason = null;
    
    const resolve = (value) => {
       if(this.status === 'pending'){
            this.status = 'resolved';
            this.value = value;
        }
    }
    const reject = (reason) =>{
        if(this.status === 'pending'){
            this.stauts = 'rejected';
            this.reason = reason;
        }
    }
    executor(resolve,reject);
}
//then
Promise.prototype.then = function(onResolved, onRejected){
    onResolved = typeof onResolved === 'function' ? onResolved : data=>data;
    onRejected = typeof onRejected === 'function ' ? onRejected : err=>{throw err};
    if(this.status === 'resolved'){
        onResolved(this.value)
    }
    if(this.status === 'rejected'){
        onRejected(this.reason)
    }
}

可以看到,resolve和reject方法中加入了判断,只允许Promise实例状态从pending变为resolved,或从pending变为rejected。

异步实现

但是Promise是用来解决异步问题的,而我们的代码全部都是同步执行的,我们从下面的示例代码入手,逐步分析。

let promise = new Promise((resolve,reject) => {
    setTimeout(() => {
        resolve('data')
    },2000)
})
promise.then(data => {
    console.log(data)
})

正常来讲,上述代码会在2s后输出data,但是现在没有输出任何信息,因为我们的实现逻辑是同步的,上述代码在实例化一个Promise构造函数时,会在setTimeout逻辑中调用resolve,也就是2s后才会调用resolve方法,更改Promise实例状态。而结合我们的实现,then方法中的onResolved是同步执行的,它在执行时this.status仍然为pending,并没有做到“2s后在执行onResolved”。

我们应该在开发者调用resolve的时刻去调用onResolved方法,那么应该先在状态为pending时把开发者传进来的onResolved方法存起来,再在resolve方法中执行即可。

function Promise(executor){
    this.status = 'pending';
    this.value = null;
    this.reason = null;
    
    this.onResolvedFunc = Function.prototype;
    this.onRejectedFunc = Function.prototype;
    const resolve = (value) => {
       if(this.status === 'pending'){
            this.status = 'resolved';
            this.value = value;
            this.onResolvedFunc(this.value);
        }
    }
    const reject = (reason) =>{
        if(this.status === 'pending'){
            this.stauts = 'rejected';
            this.reason = reason;
            this.onRejectedFunc(this.reason);
        }
    }
    executor(resolve,reject);
}
//then
Promise.prototype.then = function(onResolved, onRejected){
    onResolved = typeof onResolved === 'function' ? onResolved : data=>data;
    onRejected = typeof onRejected === 'function ' ? onRejected : err=>{throw err};
    if(this.status === 'resolved'){
        onResolved(this.value)
    }
    if(this.status === 'rejected'){
        onRejected(this.reason)
    }
    if(this.status === 'pending'){
        this.onResolvedFunc = onResolved;
        this.onRejectedFunc = onRejected;
    }
}

通过测试发现,我们的代码可以支持异步执行了!但是通过下面这个测试样例,你会发现输出顺序有问题。

let promise = new Promise((resolve,reject)=>{
    resolve('data')
})
promise.then(data=>{
    console.log(data);
})
console.log(1);
//输出结果
//data
//1

正常的话,输出顺序应该是先输出1再输出data。因此,需要将resolve和reject的执行放到任务队列中。这里姑且放到setTimeout中,保证异步执行(这样做法并不严谨,为了保证Promise属于microtasks)

const resolve = (value) => {
        setTimeout(() => {
            if (this.status === 'pending') {
                this.status = 'resolved';
                this.value = value;
                this.onResolvedFunc(this.value);
            }
        })

    }
const reject = (reason) => {
    setTimeout(() => {
        if (this.status === 'pending') {
            this.stauts = 'rejected';
            this.reason = reason;
            this.onRejectedFunc(this.reason);
        }
    })
}
executor(resolve, reject);
//输出结果
//1
//data

Promise细节完善

为Promise添加多个then方法,执行以下代码块:

let promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('data')
    },2000)
})
promise.then(data => {
    console.log(`1:${data}`);
})
promise.then(data => {
    console.log(`2:${data}`);
})

而我们的实现只会输出 2:data,这是因为第二个then方法中的onResolvedFunc会覆盖第一个then方法中的onResolvedFunc。

我们只需要将所有then方法中的onResolvedFunc储存到一个数组onResolvedArray中,在当前Promise被决议时依次执行onResolvedArray数组内的方法即可。对于onRejectedFunc同理,实现代码如下:

function Promise(executor) {
    this.status = 'pending';
    this.value = null;
    this.reason = null;

    this.onResolvedArray = [];
    this.onRejectedArray = [];
    const resolve = (value) => {
        setTimeout(() => {
            if (this.status === 'pending') {
                this.status = 'resolved';
                this.value = value;
                this.onResolvedArray.forEach(func => {
                    func(value)
                })
            }
        })

    }
    const reject = (reason) => {
        setTimeout(() => {
            if (this.status === 'pending') {
                this.stauts = 'rejected';
                this.reason = reason;
                this.onRejectedArray.forEach(func => {
                    func(reason)
                })
            }
        })
    }
    executor(resolve, reject);
}
//then
Promise.prototype.then = function (onResolved, onRejected) {
    onResolved = typeof onResolved === 'function' ? onResolved : data => data;
    onRejected = typeof onRejected === 'function ' ? onRejected : err => { throw err };
    if (this.status === 'resolved') {
        onResolved(this.value)
    }
    if (this.status === 'rejected') {
        onRejected(this.reason)
    }
    if (this.status === 'pending') {
        this.onResolvedArray.push(onResolved);
        this.onRejectedArray.push(onRejected);
    }
}

另一个需要完善的细节是,在构造函数中如果出错,将会自动出发Promise实力状态变为rejected,因此我们用try...catch块对executor进行包裹,如下:

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

到目前为止,已经初步实现了基本的Promise,得出下面一些重要结论:

  • Promise的状态具有凝固性。
  • Promise可以在then方法第二个参数中进行错误处理。
  • Promise实例可以添加多个then处理场景。

Promise then的链式调用

我们先来看一道题目,判断以下代码输出结果:
let promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('carol')
    },2000)
})
promise.then(data => {
    console.log(data);
    return `${data} next then`
}).then(data => {
    console.log(data);
})

这段代码执行后,将会在2s后输出carol,紧接着输出carol next then。Promise实例的then方法支持链式调用。

如果在第一个then方法体的onResolved函数中返回另一个Promise实例结果又是怎样的呢?来看以下代码:

let promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('carol')
    },2000)
})
promise.then(data => {
    console.log(data);
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(`${data} next then`)
        },4000)
    })
})
.then(data => {
    console.log(data);
})

上述代码将在2s后输出carol,紧接着再过4s(第6s)输出carol next then。

由此可知,一个Promise实例then方法的onResolved函数和onRejected函数是支持再次返回一个Promise实例的,也支持返回一个非Promise实例的普通值;并且,返回的这个实例/普通值将会传给下一个then方法的onResolved或onRejected。

链式调用初步实现

为了能够支持then方法的链式调用,每一个then方法的onResolved函数和onRejected函数都应该返回一个Promise实例。

先来看一个实际使用Promise链的场景,代码如下:

let promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('carol')
    },2000)
})
promise.then(data => {
    console.log(data);
    return `${data} next then`
}).then(data => {
    console.log(data);
})

这种onResolved函数会返回一个普通字符串类型的基本值,这里的onResolved函数代码如下:

data => {
    console.log(data);
    return `${data} next then`
}

在前面实现的then方法中,我们可以创建一个新的Promise实例即promise2,并最终将这个promise2返回,代码如下:

Promise.prototype.then = function (onResolved, onRejected) {
    onResolved = typeof onResolved === 'function' ? onResolved : data => data;
    onRejected = typeof onRejected === 'function ' ? onRejected : err => { throw err };
    //将promise2作为then方法返回值
    let promise2;
    if (this.status === 'resolved') {
        return promise2 = new Promise((resolve, reject) => {
            setTimeout(() => {
                try {
                    const result = onResolved(this.value);
                    resolve(result)
                } catch (error) {
                    reject(error)
                }
            })
        })
    }
    if (this.status === 'rejected') {
        return promise2 = new Promise((resolve, reject) => {
            setTimeout(() => {
                try {
                   const result = onRejected(this.reason);
                   resolve(result)
                } catch (error) {
                    reject(error)
                }
            })
        })
    }
    if (this.status === 'pending') {
        return promise2 = new Promise((resolve, reject) => {
            this.onResolvedArray.push(() => {
                try {
                    const result = onResolved(this.value);
                    resolve(result)
                } catch (error) {
                    reject(error)
                }
            });
            this.onRejectedArray.push(() => {
                try {
                    const result = onRejected(this.reason);
                    resolve(result)
                 } catch (error) {
                     reject(error)
                 }
            });
        })
    }
}

这里重点理解this.status==='pending'判断分支中的逻辑。当使用Promise实例调用其then方法时,应该返回一个Promise实例即promise2,它在异步处理结束后,依次执行onResolvedArray或onRejectedArray数组中的函数时被决议。

链式调用完善实现

我们继续实现then方法,来显示返回一个Promise实例,实现代码如下:
let promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('carol')
    },2000)
})
promise.then(data => {
    console.log(data);
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(`${data} next then`)
        },4000)
    })
})
.then(data => {
    console.log(data);
})

在之前实现的result语句中,使变量result既可以是一个普通值,也可以是一个Promise实例,为此我们抽象出resolvePromise方法进行统一处理,resolvePromise函数接收以下4个参数:

  • promise2: 返回的Promise实例。
  • result: onResolved或onRejected函数的返回值。
  • resolve: promise2的resolve方法。
  • reject: promise2的reject方法。 改动后的代码如下:
function Promise(executor) {
    this.status = 'pending';
    this.value = null;
    this.reason = null;

    this.onResolvedArray = [];
    this.onRejectedArray = [];
    const resolve = (value) => {
        setTimeout(() => {
            if (this.status === 'pending') {
                this.status = 'resolved';
                this.value = value;
                this.onResolvedArray.forEach(func => {
                    func(value)
                })
            }
        })

    }
    const reject = (reason) => {
        setTimeout(() => {
            if (this.status === 'pending') {
                this.stauts = 'rejected';
                this.reason = reason;
                this.onRejectedArray.forEach(func => {
                    func(reason)
                })
            }
        })
    }
    try {
        executor(resolve, reject);
    } catch (error) {
        reject(error)
    }
}

const resolvePromise = (promise2, result, resolve, reject) => {
    //当result和promise2相等时,也就是在onResolved返回promise2时,执行reject
    //对“死循环”进行处理
    if(result === promise2){
        reject(new TypeError('error due to circular reference'))
    }
    //是否已经执行过onResolved或onRejected
    let consumed = false;
    let thenable;
    if(result instanceof Promise){
        if(result.status === 'pending'){
            result.then(function(data){
                resolvePromise(promise2, data, resolve, reject)
            },reject)
        }else{
            result.then(resolve,reject)
        }
        return
    }

    let isComplexResult = target => (typeof target === 'object' || typeof target === 'function') && target !== null;
    //如果返回的是疑似Promise类型
    if(isComplexResult(result)){
        try {
            thenable = result.then;
            //判断返回值是否是Promise类型
            if(typeof thenable === 'function'){
                thenable.call(result,function(data){
                    if(consumed) return;
                    consumed = true;
                    return resolvePromise(promise2, data, resolve, reject)
                },function(error){
                    if(consumed) return;
                    consumed = true;
                    return reject(error)
                })
            }else{
                resolve(result)
            }
        } catch (error) {
            if(consumed) return;
            consumed = true;
            return reject(error)
        }
    }else{
        resolve(result)
    }
}

//then
Promise.prototype.then = function (onResolved, onRejected) {
    onResolved = typeof onResolved === 'function' ? onResolved : data => data;
    onRejected = typeof onRejected === 'function ' ? onRejected : err => { throw err };
    //将promise2作为then方法返回值
    let promise2;
    if (this.status === 'resolved') {
        return promise2 = new Promise((resolve, reject) => {
            setTimeout(() => {
                try {
                    const result = onResolved(this.value);
                    resolvePromise(promise2, result, resolve, reject)
                } catch (error) {
                    reject(error)
                }
            })
        })
    }
    if (this.status === 'rejected') {
        return promise2 = new Promise((resolve, reject) => {
            setTimeout(() => {
                try {
                   const result = onRejected(this.reason);
                   resolvePromise(promise2, result, resolve, reject)
                } catch (error) {
                    reject(error)
                }
            })
        })
    }
    if (this.status === 'pending') {
        return promise2 = new Promise((resolve, reject) => {
            this.onResolvedArray.push(() => {
                try {
                    const result = onResolved(this.value);
                    resolvePromise(promise2, result, resolve, reject)
                } catch (error) {
                    reject(error)
                }
            });
            this.onRejectedArray.push(() => {
                try {
                    const result = onRejected(this.reason);
                    resolvePromise(promise2, result, resolve, reject)
                 } catch (error) {
                     reject(error)
                 }
            });
        })
    }
}

Promise穿透实现

先看以下代码:
let promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('carol')
    },2000)
})
promise.then(null)
.then(data => {
    console.log(data);
})

这段代码会在2s后输出carol,这就是Promise穿透现象:给then()函数传递非函数值作为其参数时,实际上会被解析成then(null),这时上一个Promise对象的决议结果便会“穿透”到下一个then方法的参数中。

在then()方法实现中,已经为onResolved和onRejected函数加上了如下判断:

Promise.prototype.then = function(onResolved, onRejected){
    onResolved = typeof onResolved === 'function' ? onResolved : data => data;
    onRejected = typeof onRejected === 'function ' ? onRejected : err => { throw err };
    //...
}

如果onResolved不是函数类型,则给一个默认值,该默认值是返回其参数的函数,这就实现了“穿透”的作用。

Promise静态方法和其他方法实现

  • Promise.prototype.catch
  • Promise.resolve
  • Promise.reject
  • Promise.all
  • Promise.race

Promise.resolve

Promise.resolve(value)方法返回一个以给定值解析后的Promise实例对象。
Promise.resolve = function(value) => {
    return new Promise((resolve,reject) => {
        resolve(value)
    })
}

同样实现一个Promise.reject(reason),代码如下

Promise.resolve = function(reason) => {
    return new Promise((resolve,reject) => {
        reject(reason)
    })
}

Promise.all

Promise.all(iterable)方法返回一个Promise实例,此实例在iterable参数内所有Promise实例都“完成”(resolved)或参数中不包含Promise实例时完成回调(resolve);如果参数中的Promise实例有一个失败(rejected),则此实例回调失败(reject)。

先来看一个实例:

//promise.all
const promise1 = new Promise((resolve,reject) => {
    setTimeout(() => {
        resolve("carol")
    },2000)
})
const promise2 = new Promise((resolve,reject) => {
    setTimeout(() => {
        resolve("carol")
    },2000)
}) 
Promise.all([promise1,promise2]).then(data => {
    console.log(data);
})
//输出结果
//['carol', 'carol']

实现思路:

Promise.all = function(promiseArray){
    if(!Array.isArray(promiseArray)){
        throw new TypeError('The arguments should be an array!')
    }
    return new Promise((resolve,reject) => {
        try {
            let resultArray = [];
            const length = promiseArray.length;
            for(let i=0;i<length;i++){
                promiseArray[i].then(data => {
                    resultArray.push(data)
                    if(resultArray.length === length){
                        resolve(resultArray)
                    }
                },reject)
            }
        } catch (error) {
            reject(error)
        }
    })
}

先对参数promiseArray的类型进行判断,对非数组类型进行抛错。Promise.all会返回一个Promise实例,这个实例会在promiseArray中的所有Promise实例被决议后进行决议,决议结果是一个数组,存有所有Promise实例的决议值。

Promise.race

先来看一个实例:
const promise1 = new Promise((resolve,reject) => {
    setTimeout(() => {
        resolve("carol1")
    },2000)
})
const promise2 = new Promise((resolve,reject) => {
    setTimeout(() => {
        resolve("carol2")
    },4000)
}) 
Promise.race([promise1,promise2]).then(data => {
    console.log(data);
})
//输出结果(2s后)
//carol1

实现Promise.race的代码如下:

Promise.race = function(promiseArray){
    if(!Array.isArray(promiseArray)){
        throw new TypeError('The arguments should be an array!')
    }
    return new Promise((resolve,reject) => {
        try {
            const length = promiseArray.length;
            for(let i=0;i<length;i++){
                promiseArray[i].then(resolve,reject)
            }
        } catch (error) {
            reject(error)
        }
    })
}

这里是用for循环同步执行PromiseArray数组中所有Promise实例的then方法,第一个resolve的实例会直接触发新的Promise实例的resolve方法。

《前端开发核心知识进阶》——promise总结