javascript基础系列之Promise原理及模拟实现

566 阅读5分钟

回调函数

Nodejs通过回调函数来改进异步编程模型,回调模式与事件模式类似,异步代码都会在将来的某一时刻调用。(深入理解ES6)

回调函数使用时的注意点:JS中this的指向问题

回调函数的缺点(你不知的JavaScript):

1.回调函数的嵌套导致代码易读性很差,俗称回调地狱:

ajax("http://simple1.url",
    success:function(result1){
        ajax("http://simple2.url",
            success:function(result2){
                return result1 + result2;
    })
})

2.信任问题:

使用回调函数会受到控制反转的影响,把回调函数的控制权交给第三方,以下是两种情况及其应对方法:

1.传入的回调函数完全不被调用:

//解决方法
function timeoutify(fn, delay){
    let intv = setTimeout(()=>{
        intv = null;
        fn( new Error("Timeout!");
    }, delay)
    return function(){
        if(intv){
            //函数执行到此处说明定时器还在,回调函数已经被调用了
            clearTimeout(intv);
            fn.apply(this, arguments)
        }
    }
}

2.传入的回调函数提前被调用:

//解决方法:利用定时器永远异步调用回调函数
function asyncify(fn){
    let ori_fn = fn;
    let intv = setTimeout(()=>{
        intv = null;//important
        if(fn) fn();
    },0);
    fn = null;
    return function(){
        if(intv){
            //函数执行到此处说明提前调用
            //这里使用apply是利用数组传入参数?
            fn = ori_fn.bind.apply(ori_fn,[this].concat([].slice.call(arguments)));
        }else{
            //函数执行到次数说明正常调用
            ori_fn.apply(this, arguments);
        }
    }
}

Promsise

Promise的一些名词解释

  1. 内部属性[[PromiseState]]被用来表示Promise的3中状态:"pending", "resolve"(决议), "reject"(拒绝)
  2. fulfill(完成)

Promise的出现解决了上述问题

  1. 回调地狱:promise支持链式调用
  2. 调用过早:回调函数的调用永远在决议产生之后
  3. 调用过晚/不被调用:一旦决议产生后会立即调用回调函数,且不会在产生下一次异步的时候回调函数仍没有被调用

Promise的特点

  1. Promise相当于异步操作的占位符,它不会去订阅一个事件,也不会传递一个回调函数给目标函数,而是让函数返回一个Promise对象(支持链式调用)
  2. new Promise对象的时候要传入一个executor函数
  3. 回调函数的运行总是在promise决议完成后
  4. 单决议:一旦决议完成就成了不变值(immutable value),回调函数也只能被调用一次(未决议时回调函数永不调用,因此回调函数的调用不是0次就是1次)
  5. 如何改变promise的状态?resolve: pending -> resolved; reject/抛出异常: pending -> rejected;
  6. 使用then方法返回的新的promise的状态由回调函数执行的结果决定;结果可分为三种情况:1):抛出异常,新promise的状态为rejected; 2)非promise的任意值,新promise状态为resolved;3)返回一个promise,新promise的状态由回调函数产生的promise决定
  7. 异常值传透:使用then方法链式调用时,可以只写onResolved方法,一旦产生错误可最后由catch方法捕捉到
  8. 如何"中断"promise链:new Promise(()->{}),新promise实例的状态一直未pending
  9. Promise.all方法的特点:1)一个promise rejected,返回一个rejected的promise实例;2)返回值与promise数组一一对应

手写模拟Promise

function Promise(excutor){
    this.callbacks = []
    this.data = null
    const PENDING = "pending"
    const RESOLVED = "resolved"
    const REJECTED = "rejected"
    this.status = PENDING
    //成功的promise值用value表示
    function resolve(value){
        //实现单决议
        if(this.status !== PENDING) return
        
        //改变状态
        this.status = RESOLVED
        
        //保存数据
        this.data = value
        
        //调用then方法的时候,如果promise还处于pending状态就把回调函数放入cb数组中
        if(this.callbacks.length > 0){
            this.callbacks.forEach(obj=>{
                setTimeout(()=>{
                    obj.onResolved(value)
                })
            })
        }
    }
    //失败的promise值用reason表示
    function reject(reason){
        if(this.status !== PENDING) return
        this.status = REJECTED
        this.data = reason
        if(this.callbacks.length > 0){
            this.callbacks.forEach(obj=>{
                setTimeout(()=>{
                    obj.onRejected(value)
                })
            })
        }
    }
    
    //注意promise的特点,抛出错误也会改变状态
    try{
        excutor(resolve, reject)
    }catch(error){
        reject(error)
    }
    
}

Promise.prototype.then = function(onResolved, onRejected){
    //给onResolved和onRejected函数指定默认值
    onResolved = typeof onResolved == 'function' ? onResolved : value => value
    onResolved = typeof onRejected == 'function' ? onResolved : reason => { throw reason }
    
    //提取重复代码:根据上述promise特点6提取
    const handle = callback =>{
        try{
            const result = callback(this.data)
            if(result instanceof Promise){
                //当回调函数产生的值为promise对象时
                //利用then方法产生决议,此时result也可能处于pending/resolved/rejected状态
                result.then(resolve, reject)
            }else{
                //非promise的普通值,为resolved状态
                resolve(result)
            }
        }catch(error){
            //抛出错误为rejected状态
            reject(error)
        }
    }

    //每次都要返回一个promise实例以实现链式调用
    return new Promise((resolve, reject)=>{
        //箭头函数不用担心this指向问题
        if(this.status === "pending"){
            //pending状态下将回调函数保存到this.callbacks数组中,等到决议产生了再调用回调函数
            //注意:这里一定要**调用**resolve和reject函数,否则新的promise实例会一直pending
            this.callbacks.push({
                //此处的onResolved和handle(onResolved)中的onResolved不同
                onResolved(){
                    handle(onResolved)
                },
                onRejected(){
                    handle(onRejected)
                }
            })
        }else if(this.status === "resolved"){
            //利用setTimeout模拟,保证回调函数永远是异步调用的
            setTimeout(()=>{
                handle(onResolved)
            })
        }else{
            setTimeout(()=>{
                handle(onRejected)
            })
        }
    })
}

//catch方法返回的仍然是一个promise实例,promise不会在此中断
Promise.prototype.catch = function(onRejected){
    return this.then(null, onRejected)//null占位
}

上述就是Promise最关键的两个函数,理解Promise的特点对实现Promise很重要。 接下里,继续实现剩余的方法。

Promise.resolve = function(value){
    return new Promise ((resolve, reject)) =>{
        //这里也要考虑非promise值
        if(value instanceof Promise){
            //这里也是一样要调用resolve, reject以保证返回的新promise实例一定会是fulfill的
            value.then(resolve, reject)
        }else{
            resolve(value)
        }
    })
}

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

Promise.all = function(promiseArr){
    //计数器
    let resolvedCount = 0 
    //创建和promiseArr相同长度的数组,以保证异步全部完成
    let result = new Array(promiseArr.length)
    
    return new Promise((resolve, reject)=>{
            promiseArr.forEach((promise, index)=>{
                //这里还要考虑数组中的值非promise对象
                Promise.resolve(promise).then(
                    value => {
                        //异步完成后计数器才+1
                        resolvedCount ++ 
                        //利用index保证返回值与promise数组中元素的顺序一致
                        result[index] = value,
                        //异步全部完成
                        if(resolvedCount === promiseArr.length){
                            resolve(result)
                        }
                    },
                    //一个失败全部失败
                    reason => reject(reason)
                )
            })
    })
}

Promise.race = function(promiseArr){
    return new myPromise((resolve, reject) => {
        promiseArr.forEach(promise => {
            //这里利用了单决议的特性,参考上方构造函数,resolve只调用一次
            myPromise.resolve(promise).then(resolve,reject)
        })
    })
}

接下来是自定义一些方法如resolveDelay,rejectDelay

Promise.resolveDelay = function(value, time){
    return new Promise((resolve, reject)=>{
        //注意setTimeout是在executor的内部
        setTimeout(()=>{
            if (value instanceof Promise) {
                value.then(resolve, reject)
            } else { 
                resolve(value)
            }
        }, time)
    })
})

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