手写一个自己的Promise

4,348 阅读12分钟

这里我们先啰嗦一下Promise的概念:

什么是promise?

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。

那如何实现一个符合规范的Promise呢?

参考promiseA+规范总结:

我们知道promise中共有三种状态 pending 过渡态 fulfilled 完成态 rejected 失败态

promise状态改变只有两种可能

  • 过渡态=>成功态

  • 过渡态 => 失败态

过程不可逆 无法相互转化

这里来借用一张图片更容易理解

image

let promise = new Promise((resolve, reject) => {
    //这里放入我们要执行的函数,可能是同步,也可能是异步, 这里我们就来写一个异步的执行
    setTimeout(() => {
        resolve('hello');
    })
})
    
promise.then(data => {
    console.log(data);
}, err => {console.log(err)})

上面代码表示我们new一个promise实例,并异步执行 这里通过调用then方法,我们成功得到了结果

第一步 观察语法

观察原生promise用法,我们可以发现,在new Promise时候传入了一个函数,这个函数在规范中的叫法是exector 执行器 看到这里,我们先有一个大概思路,构建一个自己的Promise构造函数

// 这里我们创建了一个构造函数 参数就是执行器
function Promise(exector) {
    
}

好的,第一步完成, 重点来了,这个Promise内部到底干了什么呢 可以看到,原生的exector中传入了两个参数,第一个参数执行会让promise状态变为resolve, 也就是成功, 第二个执行会让函数变为reject状态,也就是失败

并且这两个形参执行之后都可以传入参数,我们继续完善代码 我们将这两个形参的函数封装在构造函数内部

// 这里我们创建了一个构造函数 参数就是执行器
function Promise(exector) {
    // 这里我们将value 成功时候的值 reason失败时候的值放入属性中
    let self = this;
    this.value = undefined;
    this.reason = undefined;
    
    // 成功执行
    function resolve(value) {
      self.value = value;
    }
    
    // 失败执行
    function reject(reason) {
        self.reason = reason;
    }
    
    exector(resolve, reject);
}

这里问题来了,我们知道,promise的执行过程是不可逆的,resolve和rejeact之间也不能相互转化, 这里,我们就需要加入一个状态,判断当前是否在pending过程,另外我们的执行器可能直接报错,这里我们也需要处理一下.

// 这里我们创建了一个构造函数 参数就是执行器
function Promise(exector) {
    // 这里我们将value 成功时候的值 reason失败时候的值放入属性中
    let self = this;
    // 这里我们加入一个状态标识
    this.status = 'pending';
    this.value = undefined;
    this.reason = undefined;
    
    // 成功执行
    function resolve(value) {
        // 判断是否处于pending状态
        if (self.status === 'pending') {
            self.value = value;
            // 这里我们执行之后需要更改状态
            self.status = 'resolved';
        }
    }
    
    // 失败执行
    function reject(reason) {
        // 判断是否处于pending状态
        if (self.status === 'pending') {
            self.reason = reason;
            // 这里我们执行之后需要更改状态
            self.status = 'rejected';
        }
    }
    
    // 这里对异常进行处理
     try {
        exector(resolve, reject);
    } catch(e) {
        reject(e)
    }
}

这里先留个小坑,一会我们回头来补上

好了,Promise基本就是这样,是不是很简单,这里我们先实现一个简易版,后面的功能会逐步添加进去,不要心急,继续往后看

第二步 实现链式调用

new Promise之后我们怎么去改变promise对象的状态呢, 通过前面原生的用法我们了解到,需要使用then方法, then方法分别指定了resolved状态和rejeacted状态的回调函数 那怎么知道使用哪个回调函数呢,我们刚不是在构造函数内部定义了status么,这里就用上啦,上代码

// 我们将then方法添加到构造函数的原型上 参数分别为成功和失败的回调

Promise.prototype.then = function(onFulfilled, onRejected) {
    // 获取下this
    let self = this;
    if (this.status === 'resolved') {
        onFulfilled(self.value);
    }
    
    if (this.status === 'rejected') {
        onRejected(self.reason);
    }
}

ok,我们现在可以自己运行试试

let promise = new Promise((resolve, reject) => {
     resolve("haha");
})


promise.then(data => {
    console.log(data); //输出 haha
}, err=> {
    console.log(err);
})

// 多次调用
promise.then(data => {
    console.log(data); //输出 haha
}, err=> {
    console.log(err);
})

上面可以注意到, new Promise中的改变状态操作我们使用的是同步,那如果是异步呢,我们平时遇到的基本都是异步操作,该如何解决?

这里我们需要在构造函数中存放两个数组,分别保存成功回调和失败的回调 因为可以then多次,所以需要将这些函数放在数组中 代码如下:

// 这里我们创建了一个构造函数 参数就是执行器
function Promise(exector) {
    // 这里我们将value 成功时候的值 reason失败时候的值放入属性中
    let self = this;
    // 这里我们加入一个状态标识
    this.status = 'pending';
    this.value = undefined;
    this.reason = undefined;
    // 存储then中成功的回调函数
    this.onResolvedCallbacks = [];
    // 存储then中失败的回调函数
    this.onRejectedCallbacks = [];
    
    // 成功执行
    function resolve(value) {
        // 判断是否处于pending状态
        if (self.status === 'pending') {
            self.value = value;
            // 这里我们执行之后需要更改状态
            self.status = 'resolved';
            // 成功之后遍历then中成功的所有回调函数
            self.onResolvedCallbacks.forEach(fn => fn());
        }
    }
    
    // 失败执行
    function reject(reason) {
        // 判断是否处于pending状态
        if (self.status === 'pending') {
            self.reason = reason;
            // 这里我们执行之后需要更改状态
            self.status = 'rejected';
            // 成功之后遍历then中失败的所有回调函数
            self.onRejectedCallbacks.forEach(fn => fn());
        }
    }
    
    // 这里对异常进行处理
     try {
        exector(resolve, reject);
    } catch(e) {
        reject(e)
    }
}


// then 改造

Promise.prototype.then = function(onFulfilled, onRejected) {
    // 获取下this
    let self = this;
    if (this.status === 'resolved') {
        onFulfulled(self.value);
    }
    
    if (this.status === 'rejected') {
        onRejected(self.reason);
    }
    
    // 如果异步执行则位pending状态
    if(this.status === 'pending') {
        // 保存回调函数
        this.onResolvedCallbacks.push(() => {
            onFulfilled(self.value);
        })

        this.onRejectedCallbacks.push(() => {
            onRejected(self.reason)
        });
    }
}


// 这里我们可以再次实验

let promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        if(Math.random() > 0.5) {
            resolve('成功');
        } else {
            reject('失败');
        }
    })
})

promise.then((data) => {
    console.log('success' + data);
}, (err) => {
    console.log('err' + err);
})

如何实现then的链式调用

这里要开始重点了,千万不要错过,通过以上代码,我们实现了一个简易版的promise,说简易版是因为我们的then方法只能调用一次,并没有实现原生promise中的链式调用。

那链式调用是如何实现的呢?

这里我们需要回顾下promiseA+规范,通过查看规范和阮一峰的es6讲解可以了解到

then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。

这里我们看一段原生promise代码

getJSON("/posts.json").then(function(json) {
  return json.post;
}).then(function(post) {
  // ...
});

上面的代码使用then方法,依次指定了两个回调函数。 第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。

采用链式的then,可以指定一组按照次序调用的回调函数。这时,前一个回调函数,有可能返回的还是一个Promise对象(即有异步操作),这时后一个回调函数,就会等待该Promise对象的状态发生变化,才会被调用。

另外通过原生的promise我们还可以发现,上一次的成功或者失败在返回值是一个普通类型数据的时候,都走向了下一次then的成功回调,我们可以继续改造then方法

Promise.prototype.then = function(onFulfilled, onRejected) {
    // 获取下this
    let self = this;
    // 因为then方法返回的是一个promise,这里我们新建一个promise
    let promise2 = new Promise((resolve, reject) => {
        if (this.status === 'resolved') {
            //获取回调的返回值
            try {
                // 当执行成功回调的时候 可能会出现异常,那就用这个异常作为promise2的错误的结果
                let x = onFulfilled(self.value);
                //执行完当前成功回调后返回结果可能是promise
                resolvePromise(promise2,x,resolve,reject);
            } catch (e) {
                reject(e);
            }
        }
    
        if (this.status === 'rejected') {
             //获取回调的返回值
             try {
                let x = onRejected(self.reason);
                resolvePromise(promise2,x,resolve,reject);
            } catch (e) {
                reject(e);
            }
        }
        
        // 如果异步执行则位pending状态
        if(this.status === 'pending') {
            // 保存回调函数
            this.onResolvedCallbacks.push(() => {
                 //获取回调的返回值
                 try {
                    let x = onFulfilled(self.value);
                    resolvePromise(promise2,x,resolve,reject);
                } catch (e) {
                    reject(e);
                }

            })

            this.onRejectedCallbacks.push(() => {
                 //获取回调的返回值
                 try {
                    let x = onRejected(self.reason);
                    resolvePromise(promise2,x,resolve,reject);
                } catch (e) {
                    reject(e);
                }
            });
        }
    })
    
    return promise2;
}

这里我们看下新的then函数有什么变化,我们一步一步分析,首先,新建了一个promise并返回,这里是根据原生promise文档得知: then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。

这里理解之后我们继续看,内部我们又获取了本次then方法成功或者失败回调之后的返回值,赋值给变量x,这里就会出现几种情况,变量x可能为普通值,也可能为一个promise

我们定义了一个resolvePromise函数,将then返回的promise, 本次成功或者失败的返回值,已经then返回promise的两个参数传输这个函数中,进行一些判断,具体实现如下:

function resolvePromise(promise2,x,resolve,reject){
    // promise2和函数执行后返回的结果是同一个对象
    
    if(promise2 === x){
        return reject(new TypeError('Chaining cycle'));
    }
    // x可能是一个promise 或者是一个普通值
    if(x!==null && (typeof x=== 'object' || typeof x === 'function')){
        try{
            let then = x.then; 
            // 取对象上的属性 怎么能报异常呢?(这个promise不一定是自己写的 可能是别人写的 有的人会乱写)
            // x可能还是一个promise 那么就让这个promise执行即可
            // {then:{}}
            // 这里的逻辑不单单是自己的 还有别人的 别人的promise 可能既会调用成功 也会调用失败
            if(typeof then === 'function'){
                then.call(x,y=>{ // 返回promise后的成功结果
                    // 递归直到解析成普通值为止
                    // 递归 可能成功后的结果是一个promise 那就要循环的去解析
                    resolvePromise(promise2,y,resolve,reject);
                },err=>{ // promise的失败结果
                    reject(err);
                });
            }else{
                resolve(x);
            }
        }catch(e){
            reject(e);
        }
    }else{ // 如果x是一个常量
        resolve(x);
    }
}

看的这里是不是有点蒙圈,没关系,我们继续分析这个实现。

then返回一个promise ?

首选进入函数内部,我们判断promise2是不是等于x, 这个相当于判断上次then的返回值是不是成功和回调的返回值,这样就是陷入死循环,举个例子:

let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('hello');
    })
})

let p2 = p.then(data => {
    return p2;
})

这种写法就会陷入一个死循环 所以要避免这种情况发生。 好的,继续往下看,刚才说到x可能是一个普通值,也可能是一个promise,所以函数内部就要做一个判断,是否是一个promise, 如果返回的是一个promise,那么需要继续执行这个promise,这里用了递归。 平时使用promise时候我们也会注意到,各种promise库可能会混用,所以内部对这个then的类型进行了判断。

Ok,到这里是不是理解了一些了,我们继续往下看,我们知道同一个Promise内部的状态是无法相互转化的,这里需要在内部做一个判断。

function resolvePromise(promise2,x,resolve,reject){
    // promise2和函数执行后返回的结果是同一个对象
    
    if(promise2 === x){
        return reject(new TypeError('Chaining cycle'));
    }
    let called;
    // x可能是一个promise 或者是一个普通值
    if(x!==null && (typeof x=== 'object' || typeof x === 'function')){
        try{
            let then = x.then; // 取对象上的属性 怎么能报异常呢?(这个promise不一定是自己写的 可能是别人写的 有的人会乱写)
            // x可能还是一个promise 那么就让这个promise执行即可
            // {then:{}}
            // 这里的逻辑不单单是自己的 还有别人的 别人的promise 可能既会调用成功 也会调用失败
            if(typeof then === 'function'){
                then.call(x,y=>{ // 返回promise后的成功结果
                    // 递归直到解析成普通值为止
                    if(called) return; // 防止多次调用
                    called = true;
                    // 递归 可能成功后的结果是一个promise 那就要循环的去解析
                    resolvePromise(promise2,y,resolve,reject);
                },err=>{ // promise的失败结果
                    if(called) return;
                    called = true;
                    reject(err);
                });
            }else{
                resolve(x);
            }
        }catch(e){
            if(called) return;
            called = true;
            reject(e);
        }
    }else{ // 如果x是一个常量
        resolve(x);
    }
}

我们加入一个called变量,防止互相转化。 代码写到这里是不是就完了? 当然没有,细心的同学会发现,原生promise还有一个用法

let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('hello');
    })
})


p.then().then(data => {
    console.log(data);
    throw new Error('e');
}).then().then(null, err => {
    console.log(err);
})

这种用法会发生值穿透,当上一个then函数没有调用成功和失败回调的时候,值会传递进下一次then调用。

这个怎么实现的呢,其实很简单,我们只需要判断每次then调用的时候是否传入了成功或者失败的回调,没有回调,就继续返回上轮then成功或者失败传入的值。 我们还了解到,then方法的回调都是异步执行的,这里我们简单用定时器模仿下,当然内部实现可不是这么简单。这里仅作为简单实现

代码如下

Promise.prototype.then = function (onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function'?onFulfilled:val=>val;
    onRejected = typeof onRejected === 'function'?onRejected: err=>{throw err}
    let self = this;
    let promise2;
    promise2 = new Promise((resolve, reject) => {
        if (self.status === 'resolved') {
            setTimeout(()=>{
                try {
                    let x = onFulfilled(self.value);
                    resolvePromise(promise2,x,resolve,reject);
                } catch (e) {
                    reject(e);
                }
            },0)
        }
        if (self.status === 'rejected') {
            setTimeout(()=>{
                try {
                    let x = onRejected(self.reason);
                    resolvePromise(promise2,x,resolve,reject);
                } catch (e) {
                    reject(e);
                }
            },0)
        }
        if (self.status === 'pending') {
            self.onResolvedCallbacks.push(() => {
                setTimeout(()=>{
                    try {
                        let x = onFulfilled(self.value);
                        resolvePromise(promise2,x,resolve,reject);
                    } catch (e) {
                        reject(e);
                    }
                },0)
            });
            self.onRejectedCallbacks.push(() => {
                setTimeout(()=>{
                    try {
                        let x = onRejected(self.reason);
                        resolvePromise(promise2,x,resolve,reject);
                    } catch (e) {
                        reject(e);
                    }
                },0)
            });
        }
    });
    return promise2
}

大工告成。

image
等等,是不是少点什么,你是不是在逗我,少侠莫急。且继续往下看。 我们平时使用当然还有promise的一些其他方法,如catch all race。 而且还能这么写

Promise.resove().then()
Promise.reject().then()

Promise的补充

我们一个一个来实现,就先看上面直接在Promise类上调用成功和失败 我们可以这么写

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

catch呢, 相当于直接走入下一次then的失败回调

Promise.prototype.catch = function(onRejected){
    // 默认不写成功
    return this.then(null,onRejected);
};

Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。 具体用法可以参考es6文档,这就不具体再说用法

// 传入一个promise数组
Promise.all = function(promises){
// 返回执行后的结果
    return new Promise((resolve,reject)=>{
        let arr = [];
        let i = 0;
        function processData(index,data){
            arr[index] = data;
            // 判断是否全部成功
            if(++i == promises.length){
                resolve(arr);
            }
        }
        for(let i = 0;i<promises.length;i++){
            promises[i].then(data=>{ // data是成功的结果
                //将每次执行成功后的结果传入函数
                processData(i,data);
            },reject);
        }
    })
}

race就更简单了。

Promise.race = function(promises){
    return new Promise((resolve,reject)=>{
        for(let i = 0;i<promises.length;i++){
            promises[i].then(resolve,reject);
        }
    })
}

这里我们就已经实现了promise常见的一些功能,这里需要多看几遍加深记忆。