简单实现Promise的原理

时间: 作者:admin 浏览:

Promise对象的出现,是为了解决异步请求回调嵌套(地狱回调)的问题,它的实现原理很简单,从嵌套回调到链式回调的转换,其实就是形式的改变,最关键的因素是增加了状态判断,状态贯穿Promise整个对象;好吧,下面我们从最初的地方开始讲起:异步回调嵌套

还记得jquery时代的ajax请求吗?

$.ajax({
    type:"post",
    url:"api/getDatas",
    async:true,
    data:{status: 1},
    success:function(res){
        //请求成功的回调
    },
    error:function(err){
        //请求失败的回调
    }
})

其实这样写一点问题都没有,但如果在成功的回调里再次嵌套一次异步请求,那么代码就成了这样:

$.ajax({
    type:"post",
    url:"api/getDatas“,
    async:true,
    data:{status: 1},
    success:function(res){
        $.ajax({
            type:"post",
            url:"api/getDatas“,
            async:true,
            data:{status: 1, value: res.id},
            success:function(res){
                //第二次请求成功的回调
            },
            error:function(err){
                //第二次请求失败的回调
            }
        })
    },
    error:function(err){
        //第一次请求失败的回调
    }
})

之所以有这样的写法,很多情况是因为第二次请求的参数需要通过第一次请求获取,比如上面的value: res.id就是如此;

为了解决嵌套的问题,我们可以先自己尝试下自己的方法,既然不能嵌套,那我就把回调里的内容拿出来外面,分成两个方法:

function execute(success, error){
    $.ajax({
        type:"post",
        url:"api/getDatas",
        async:true,
        data:{status: 1},
        success:function(res){
            success()//先不做类型判断
        },
        error:function(err){
            error()
        }
    })
}
function execute2(success2, error2){
    $.ajax({
        type:"post",
        url:"api/getDatas2",
        async:true,
        data:{status: 2},
        success:function(res){
            success2()//先不做类型判断
        },
        error:function(err){
            error2()
        }
    })
}
let success = function(res){
    execute2()
}
let error = function(err){
    //...
}
execute(success, error)

这样定义好后,想再执行一次异步,只需要在success方法里执行execute2()方法(暂时忽略参数),以及多定义两个回调方法即可:

function execute(success, error){
    $.ajax({
        type:"post",
        url:"api/getDatas",
        async:true,
        data:{status: 1},
        success:function(res){
            success()//先不做类型判断
        },
        error:function(err){
            error()
        }
    })
}
function execute2(success2, error2){
    $.ajax({
        type:"post",
        url:"api/getDatas2",
        async:true,
        data:{status: 2},
        success:function(res){
            success2()//先不做类型判断
        },
        error:function(err){
            error2()
        }
    })
}
let success2 = function(res){
    //2
}
let error2 = function(err){
    //2
}
let success = function(res){
    //1
    execute2(success2, error2)
}
let error = function(err){
    //1
}
execute(success, error)

代码是不是就这样分离开来了,再嵌套只需要重复上面的步骤即可,嵌套的问题自然就解决了,但是代码这样写结构比较松散,因为这种结构从形式上跟构造器+原型链的模式很相似,所以我们可以利用面向对象的方法来整理代码,写成这样:

function Promise(callback){
    this.res = null
    this.err = null
    this.status = "0"
    this.resolve = (res)=>{
        this.res = res
        this.status = "1"
    }
    this.reject = (err)=>{
        this.err = err
        this.status = "2"
    }
    callback(this.resolve, this.reject)
}
Promise.prototype.then = function(res){
    if(this.status==="1"){
        typeof res === 'function' && res(this.res)
    }
    return this
}
Promise.prototype.catch = function(err){
    if(this.status==="2"){
        typeof err === 'function' && err(this.err)
    }
    return this
}

可以看到,我们将execute换成了callback,success换成了resolve,error换成了reject,另外还增加了status来标记成功与失败的状态,成功时同步给this.res赋值,失败时同步给this.err赋值,新增链式写法的then和catch回调,下面我们来看看用法:

将execute方法给Promise来运行:

let promise = new Promise(execute)
promise.then(function(res){
    console.log(res)
})

可以看到,这样就变成了链式的写法,当然你也可以用class{}类的写法来实现;

但是这里有个问题:

  • 如果execute是同步函数

       function execute(success, error){
          success(666)
      }
    

    new完以后会先执行resolve回调然后再执行then方法;

  • 但如果execute是异步函数

      function execute(success, error){
          setTimeout(()=>{
              success(666)
          },1000)
      }
    

    new完以后就会先执行then再到异步回调resolve

这样一来,就没办法保证异步链式写法的执行顺序了,怎么办呢?
那就在resolve里面执行then方法的回调,那then方法用来干嘛呢?用来注册将要执行的回调方法!于是就要定义多两个构造属性用来存储then与catch回调方法,同时,为了保证同步异步执行的顺序一致,都是先执行then再到resolve,利用setTimeout来执行callback:

//为了代码简洁直观,暂不作细致判断
function Promise(callback){
    this.res = null
    this.err = null
    this.status = "0"
    this.onThenBack = null
    this.onCatchBack = null
    this.resolve = (res)=>{
        this.res = res
        this.status = "1"
        this.onThenBack(this.res)
    }
    this.reject = (err)=>{
        this.err = err
        this.status = "2"
        this.onCatchBack(this.err)
    }
    setTimeout(()=>{
        callback(this.resolve, this.reject)
    }, 0)
}
Promise.prototype.then = (res)=>{
    typeof res === 'function'?this.onThenBack = res : ""
}
Promise.prototype.catch = (err)=>{
    typeof err === 'function'?this.onCatchBack = err : ""
}

到这里就基本完成了then的链式写法,试着执行下面的代码:

function execute(success, error){
    setTimeout(()=>{
        success(666)
    },3000)
}
new Promise(execute).then((res)=>{ alert(res) })

可以测试,上面的代码是可以在3s后执行then里的回调的(实际测试要将Promise改个名字);

到这里就完成了Promise的第一步:将回调写在外面,即then的链式写法;

实际中使用Promise的时候,代码执行时间确实是有些许延迟,因为Promise源码中确实使用了setTimeout来执行代码,在前端性能优化的过程中,发现,从发起接口请求到请求拦截器之间,是会延迟100ms左右的时间,这是小编在做极致性能优化过程中追踪到的,没想到是Promise的锅

(未完待续)
Promise

前端新手交流群
欢迎加入web前端新手交流qq群:
734802480(已满)、 794324979

更多文章

栏目文章


Copyright © 2014-2022 seozhijia.net 版权所有-粤ICP备13087626号-4