Promise的那些事儿

203 阅读7分钟

形象的认识一下Promise

我们都知道在JavaScript中,代码都是单线程执行的,就是由上而下的依次执行,但是有些事情像网络操作、浏览器事件,都是异步执行的,简单来说就是当某个条件触发了才会去执行相应的方法函数(回调函数),就像这个例子,1秒后就会去执行对应的回调函数。

setTimeout(()=>{
    console.log('执行了')
},1000)

那我们可以想象一下,如果说我去做一件事情要经过很多步骤,并且他们都是关联的,也就是做了这件事之后才能去做下一件事,而且还是异步的,可以瞧一下这个例子:

setTimeout(()=>{
    console.log('我饿了')
    setTimeout(()=>{
        console.log('点了个外卖')
        setTimeout(()=>{
            console.log('外卖到了,我要吃饭了')
            setTimeout(()=>{
                console.log('我吃饱了,睡个觉...')
            },10000)
        },20000)
    },1000)
},1000)

看上去是不是就像个阶梯,想要从一楼走到二楼,就得一个台阶一个台阶往上走,走的楼层多了,累了,自己都会有点懵逼,不知道自己到哪里了,为了解决这个问题呢?就有了电梯,那想要从一层到100层也是可以轻松搞定的是吧,那 Promise 就是那个电梯,坐上它不仅可以轻松地看到自己到了几层楼,还能通知每层的负责人去做相应的事情,假如说你需要去收齐1-100层所有的文件送到100层的老总那里,这时候你就可以坐上电梯到了一楼,等待一层的负责人送给你文件后,再到第二层...依次收齐最后到100层把文件送到老总那里,看上去是不是既轻松又有调理。就像这个:

function retPromise(msg){
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{
            resolve(msg)
        },1000)
    })
};
let promise = retPromise("我坐上了电梯");
promise
.then(res=>{
    return retPromise(res+'一层楼的文件')
})
.then(res=>{
    return retPromise(res+'二层楼的文件')
},err=>{
    console.log('呦!出故障了')
})
.then(res=>{
    return retPromise(res+'三层楼的文件')
})
.then(res=>{
    //我到100层了,也收集好了所有的文件
    console.log(res) // 我坐上了电梯一层楼的文件二层楼的文件三层楼的文件
})

看上去是不是清晰了很多,最起码一眼就知道了我们一步一步的做了哪些什么事情,详细的用法可以去看看阮一峰老师写的ES6入门Promise,接下来我们分析一下Promise为什么能够做这些事情~

Promise原理剖析

  • 特性,推荐先看一看Promise A+ 规范
    1. 它是一个类;
    2. 有三个状态:fulfilled(成功),rejected(失败),pending(等待中);
    3. 只有pending的时候可以改变状态,并且一旦改变完状态不可以再次更改;
    4. 实例化的时候会首先执行传入的函数;
    5. 实例有then方法;
    6. 实例能够链式调用;
  • 简易代码实现
    //声明三个状态
    const PENDING = 'pending'
    const FULFILLED = 'fulfilled'
    const REJECTED = 'rejected'
    
    class Promise {
        constructor(executor) {
            this.status = PENDING;
            let resolve = (value)=>{
                //只有当状态为pending的时候才能改变
                if(this.status === PENDING){
                    this.status = FULFILLED;
                    this.value = value
                }
            }
            let reject = reason=>{
                if(this.status===PENDING){
                    this.status = REJECTED;
                    this.reason = reason;
                }
            }
            //如果说传入的函数执行中报错了直接reject结束
            try{
                executor(resolve,reject)
            }catch(err){
                reject(err)
            }
        }
        then(onFulfilled,onRejected){
            if(this.status===FULFILLED){
                onFulfilled(this.value)
            }
            if(this.status===REJECTED){
                onRejected(this.reason)
            }
        }
    }
    
    
    上面这段代码执行时,如果executor中没有指明是reject还是resolve的时候,那执行then方法的时候就是pending的状态,此时onFulfilledonRejected是不能执行的,那会在这里卡住,所以我们需要判断当状态为pending的时候先把对应的onFulfilledonRejected函数存起来,如果后面状态发生了更改,在一次性的去执行相应队列里面的函数,另外then函数也是不可以链式调用的,并且executor里面执行异步代码里改变状态的时候,then方法就已经先执行了,所以我们就需要运用一下宏任务微任务来调整一下函数的执行顺序,应该是resolve之后去执行相应的onFulfilled函数。
    new Promise((resolve,reject)=>{
        console.log('hello')
        setTimeout(()=>{
         resolve('dsdfd')   
        })
    }).then(()=>{
        console.log('发现没有执行这里')
    })
    

代码稍加更改:

    //声明三个状态
    const PENDING = 'pending'
    const FULFILLED = 'fulfilled'
    const REJECTED = 'rejected'

    class Promise {
        constructor(executor) {
            this.status = PENDING;
            // 存储成功时的回调
            this.onFulfilledCallback = [] //++++
            // 存储失败时的回调
            this.onRejectedCallback = [] //++++
            let resolve = (value)=>{
                //只有当状态为pending的时候才能改变
                if(this.status === PENDING){
                    this.status = FULFILLED;
                    this.value = value
                    this.onFulfilledCallback.forEach(cb=>cb()) //++++
                }
            }
            let reject = reason=>{
                if(this.status===PENDING){
                    this.status = REJECTED;
                    this.reason = reason;
                    this.onRejectedCallback.forEach(cb=>cb()) //++++
                }
            }
            //如果说传入的函数执行中报错了直接reject结束
            try{
                executor(resolve,reject)
            }catch(err){
                reject(err)
            }
        }
        then(onFulfilled,onRejected){
            // 返回一个新的promise
            let promise2 = new Promise((resolve,reject)=>{ //++++
                if(this.status===FULFILLED){
                    // 这里就是为了解决executor函数里有异步函数执行改变状态的时候保证then里面的回调函数能够对应执行
                    setTimeout(()=>{ //++++
                        try{
                            onFulfilled(this.value)
                        } catch(err){
                            reject(err)
                        }
                    },0)
                }
                if(this.status===REJECTED){
                    setTimeout(()=>{
                        try{
                            onRejected(this.reason)
                        }catch(err){
                            reject(err)
                        }
                    },0)
                }
                if(this.status===PENDING){
                    this.onFulfilledCallback.push(()=>{
                        setTimeout(()=>{
                            try{
                                onFulfilled(this.value)
                            }catch(err){
                                reject(err)
                            }
                        },0)
                    })
                    this.onRejectedCallback.push(()=>{
                        setTimeout(()=>{
                            try{
                                onRejected(this.reason)
                            }catch(err){
                                reject(err)
                            }
                        },0)
                    })
                }
            })
            return promise2;
        }
    }

现在就可以执行链式调用了,异步的一些任务对应的方法也可以正常运作了,但是当executor函数或者then函数里面的onFulfilledonRejected方法执行的时候,如果return一个Promise实例或者一个值的时候是不能进行向下传递的,所以我们就应该对返回的值做一些操作,并且当onFulfilledonRejected不是一个函数的时候,应该是也能正常执行的,就是值穿透,再次对代码做一些更改。

完整代码

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'


// 对返回值x进行各种状态下的判断
function resolvePromise(promise2,x,resolve,reject){
    if(x===promise2) return reject(new TypeError('Chaning cycle detected for promise '));
    // 如果返回结果是一个promise
    if((x&&typeof x === 'object')|| typeof x === 'function'){
        let used;
        try {
            let then = x.then;
            if(typeof then === 'function'){
                then.call(x,y=>{
                    if(used) return;
                    used = true;
                    resolvePromise(promise2,y,resolve,reject)
                },r=>{
                    if(used) return
                    used = true;
                    reject(r)
                })
            }else{
                if(used) return;
                used = true;
                resolve(x)
            }
        }catch(err){
            if(used) return;
            used = true;
            reject(err)
        }
    }else{
        resolve(x)
    }
}

class Promise {
    constructor(executor) {
        this.status = PENDING;
        this.onFulfilledCallback = []
        this.onRejectedCallback = []
        let resolve = (value)=>{
            if(this.status === PENDING){
                this.status = FULFILLED;
                this.value = value
                this.onFulfilledCallback.forEach(cb=>cb())
            }
        }
        let reject = reason=>{
            if(this.status===PENDING){
                this.status = REJECTED;
                this.reason = reason;
                this.onRejectedCallback.forEach(cb=>cb())
            }
        }
        try{
            executor(resolve,reject)
        }catch(err){
            reject(err)
        }
    }
    then(onFulfilled,onRejected){
        // 值穿透,如果不是函数那我们就把它改造为函数让它能够正常的传递下去
        onFulfilled = typeof onFulfilled === 'function'?onFulfilled:res=>res;
        onRejected = typeof onRejected === 'function'?onRejected:(err)=>{throw err};
        let promise2 = new Promise((resolve,reject)=>{
            if(this.status===FULFILLED){
                setTimeout(()=>{
                    try{
                        const x = onFulfilled(this.value)
                        resolvePromise(promise2,x,resolve,reject)
                    } catch(err){
                        reject(err)
                    }
                },0)
            }
            if(this.status===REJECTED){
                setTimeout(()=>{
                    try{
                        const x = onRejected(this.reason)
                        resolvePromise(promise2,x,resolve,reject)
                    }catch(err){
                        reject(err)
                    }
                },0)
            }
            if(this.status===PENDING){
                this.onFulfilledCallback.push(()=>{
                    setTimeout(()=>{
                        try{
                            const x = onFulfilled(this.value)
                            resolvePromise(promise2,x,resolve,reject)
                        }catch(err){
                            reject(err)
                        }
                    },0)
                })
                this.onRejectedCallback.push(()=>{
                    setTimeout(()=>{
                        try{
                            const x = onRejected(this.reason)
                            resolvePromise(promise2,x,resolve,reject)
                        }catch(err){
                            reject(err)
                        }
                    },0)
                })
            }
        })
        return promise2;
    }
    catch(errFn){
        return this.then(null,errFn)
    }
    finally(fn){
        return this.then(fn,fn)
    }
}

里面的catch和finally方法其实就是对then方法参数的改装,最终执行的还是then方法。 最后还需要添加一段代码,测试一下看看是否符合Promise A+规范:

Promise.deferred = ()=>{
    let dfd = {}
    dfd.promise = new Promise((resolve,reject)=>{
        dfd.resolve = resolve;
        dfd.reject = reject;
    })
    return dfd;
}

执行npx promises-aplus-tests promise.js如果全部为绿色则通过了这个规范

Promise.all的实现

function isPromise (p) {
    return p instanceof Promise
}
Promise.all = (promises)=>{
    return new Promise((resolve,reject)=>{
        let ret = [];
        let i = 0;
        function processPromise(index,value){
            ret[index] = value;
            if(++i === promises.length){
                resolve(ret)
            }
        }
        for(let i =0;i<promises.length;i++){
            if(isPromise(promises[i])){
                promises[i].then(data=>{
                    processPromise(i,data)
                },(err)=>{
                    reject(err)
                    return
                })
            }else{
                processPromise(i,promises[i])
            }
        }
    })
}

Promise.race的实现

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

哪里找源码?

GitHub地址 欢迎star^~^