面试必深问之Promise原理与手写

发布时间:2023-05-16 13:30

✅作者简介:大家好我是无处不楼台,是一个什么都会一点的大前端小白博主!
个人主页:无处不楼台的博客_CSDN博客-JavaScript学习,项目开发,node学习领域博主
系列专栏:知识体系梳理
如果觉得博主的文章还不错的话,请三连支持一下博主哦  

\"面试必深问之Promise原理与手写_第1张图片\"

目录

前言

一、异步编程发展史

1、回调函数时代

2、Promise时代

3、co+Generator

4、async+await

二、回调地域的终结

1)、回调函数延迟绑定

2)、返回值穿透

3)、错误冒泡

三、微任务的引入

四、链式调用的实现

五、resolve、reject以及finally

1、实现Promise.resolve:

2、实现Promise.reject

3、实现Promise.prototype.finally

六、实现Promise的all和race

1、实现Promise.all

2、实现promise.race:

结束语


前言

        要深入了解promise,我们首先要明白为什么会出现promise,以及promise在js异步编程中的地位;

一、异步编程发展史

1、回调函数时代

        在最初期,为了解决js异步问题,我们在代码中大量使用回调函数,期待在一个函数执行后再触发另一个回调函数;

        但是如果在回调当中嵌套大量其他回调,就会出现回调地域;

        因为回调地域嵌套的回调层级太多,这种代码的可读性和可维护性都是非常差,

fs.readFile(\'1.json\', (err, data) => { 

    fs.readFile(\'2.json\', (err, data) => { 

        fs.readFile(\'3.json\', (err, data) => { 

            fs.readFile(\'4.json\', (err, data) => { 

            }); 

        }); 

    }); 

});

        而且如果任务失败,需要对里面的每一个任务的失败情况进行处理,增加了代码的混乱程度;

2、Promise时代

        ES6中的promise就很好的解决了回调地域的问题,同时合并了错误的处理;

readFilePromise(\'1.json\').then(data => { 

    return readFilePromise(\'2.json\') 

}).then(data => { 

    return readFilePromise(\'3.json\') 

}).then(data => { 

    return readFilePromise(\'4.json\') 

});

3、co+Generator

        ES6同时也提供了generator解决问题,generator利用协程控制代码的执行顺序,co库让代码顺序执行;

co(function* () { 

    const r1 = yield readFilePromise(\'1.json\'); 

    const r2 = yield readFilePromise(\'2.json\'); 

    const r3 = yield readFilePromise(\'3.json\'); 

    const r4 = yield readFilePromise(\'4.json\'); 
    
})

4、async+await

        async+await是ES7新增的关键字,凡是加上async的函数都默认返回一个promise对象,而更重要的是async+await也能让异步代码以同步的方式来书写,而不需要第三方库的支持;


二、回调地域的终结

        Promise利用了三大技术手段来解决回调地域:

1)、回调函数延迟绑定

        在promise中,回调函数不是直接声明的,而是通过后面的then方法传入的,即延迟传入;

2)、返回值穿透

        我们会根据then中回调函数的传入值来创建不同类型的promise,然后把返回的promise穿透到外层,以供后续的调用;

        回调函数延迟绑定和返回值穿透结合产生了链式调用的效果;

3)、错误冒泡

        promise会使用catch捕捉错误,前面promise产生了错误会一直冒泡到最后面被catch捕捉


三、微任务的引入

        promise中的执行函数都是同步进行的,但是里面存在着异步操作,在异步操作结束后会调用resolved方法,或者中途遇到错误调用reject方法,这两者都是作为微任务进入到事件循环中的,那为什么promise要引入微任务的方式来进行回调操作呢?

        这其实就是如何处理回调的问题;

1)、使用同步回调,直到异步任务进行完,再进行后面的任务

2)、使用异步回调,将回调函数放在宏任务队列的队尾

3)、使用异步回调,将回调函数放到当前宏任务中的最后面。

        第一种方式显然不可取,因为同步的问题非常明显,会让整个脚本阻塞住,当前任务等待,后面的任务都得不到执行,而这部分等待的时间是可以拿来完成其他事情的,导致CPU的利用率非常低,而且无法实现延迟绑定;

        如果采用第二种方式,那么执行回调(resolved/reject)的时机应该是在前面的所有宏任务完成之后,倘若现在的任务队列很长,那么回调迟迟得不到执行,会造成应用卡顿;

        为了解决问题,也考虑promise延迟绑定的需求,promise1采用第三种方式,即引入微任务,即把resolve(reject)回调的执行放在宏任务的末尾;

        这样微任务就解决了两大痛点:

1)、采用异步回调替代同步回调解决了浪费CPU性能的问题;

2)、放到当前宏任务最后执行,解决了回调执行的实时性问题;


四、链式调用的实现


        前面说过,Promise的返回值穿透和回调函数延迟绑定两个特性实现了链式调用,这里我们具体说说:
        第一版代码:(不是最终版本,后面会讨论如何改进)

const PENDING=\"pending\";
const FULFILLED=\"fulfilled\";
const REJECTED=\"rejected\";
function MyPromise(executor){
  let self=this;//取出promise实例
  self.value=null;//定义vlaue值
  self.error=null;//
  self.status=PENDING;
  self.onFulfilled=null;//成功的回调函数
  self.onRejected=null;//失败的回调函数
  
  const resolve=(value)=>{
    if(self.status!==PENDING)return;
        setTimeout(()=>{
          self.status=FULFILLED;
          self.value=value;
          self.onFilfilled(self.value);
        })
      }
  
  const reject=(error)=>{
    if(self.status!==PENDING)return;
    setTimeout(()=>{
      self.status=REJECTED;
      self.error=error;
      self.onRejected(self.error);
    })
  }
  executor(resolve,reject);
}
MyPromise.prototype.then=function(onFilfilled,onRejected){
  if(this.status===PENDING){
    this.onFulfilled=onFulfilled;
    this.onRejected=onRejected;
  }else if(this.status==FULFILLED){
    onFulfilled(this.value);
  }else{
    onRejected(this.error);
  }
  return this;
}

        我们都知道,promise的本质是一个有限状态机,存在三种状态:
1)、pending
2)、fulfilled
3)、rejected
        对于promise而言,状态的改变是不可逆的,即由等待态变为其他的状态后,就无法再次改变了;

        现在我们对于上面那一版promise进行一些优化:
1)、在第一版的promise中,如果我们对于一个promise实例延迟绑定多个回调函数,那么也只能执行一个;
        所以我们做出以下修改:

self.onFulfilledCallbacks=[];
self.onRejectedCallbacks=[];
MyPromise.prototype.then=function(onFulfilled,onRejected){
  if(this.status===PENDING){
    this.onFulfilledCakkbacks.push(onFulfilled);
    this.onRejectedCallbacks.push(onRejected);
  }else if(this.status==FULFILLED){
    onFulfilled(this.value);
  }else{
    onRejected(this.value);
  }
  return this;
}


2)、由于then中我们返回的是this,这会导致在链式调用时,每一个返回的都是第一个promise实例,修改如下:

MyPromise.prototype.them=function(onFulfilled,onRejected){
  let bridgePromise;
  let self=this;
  if(self.status===PENDING){
    return bridgePromise=new Mypromise((resolve,reject)=>{
      self.onFulfilledCallbacks.push((value)=>{
        try{
          let x=onFulfilled(value);
          resolved(x);
        }catch(e){
          reject(e);
        }
      });
      self.onRejectedCallbacks.push((error)=>{
        try{
          let x=onRejected(error);
          resolved(x);
        }catch(e){
          reject(e);
        }
      })
    })
  }
}

除此之外,还需要对返回promise的情况进行处理:

funciton resolvePromise(bridgePromise,x,resolve,reject){
  if(x instanceof MyPromise){
    if(x.status==PENDING){
      x.then(y=>{
        resolvePromise(bridgePromise,y,resolved,reject);
      },error=>{
        reject(error);
      })
    }else{
      x.then(resolved,reject);
    }
  }else{
    resolve(x);
  }
}

五、resolve、reject以及finally


1、实现Promise.resolve


        实现resolve静态方法有三个要点:
1)、如果传参为promise,直接返回他;
2)、如果传参为一个thenable对象,返回的promise对象会跟随着这个对象,采用它的最终状态作为子级的状态
3)、其他情况下,直接返回以该值为成功状态的promise对象

Promise.resolved=(param)=>{
  if(param instanceof Promise) return param;
  return new Promise((resolved,reject)=>{
    if(param&¶m.then&¶m.then===\'function){
      param.then(resolve,reject);
    }else{
      resolve(param);
    }
  })
}

2、实现Promise.reject


        Promise.reject中传入的参数会作为一个reason原封不动地往下传:

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

3、实现Promise.prototype.finally


        无论当前promise是成功还是失败,调用finally之后都会执行finally中传入的函数,并将值原封不动的往下传;

Promise.prototype.finally=function(callback){
  this.then(value=>{
    return Promise.resolve(callback()).then(()=>{
      return value;
    })
  },error=>{
    return Promise.resolve(callback()).then(()=>{
      throw error;
    })
  })
}

六、实现Promise的all和race


1、实现Promise.all


        对于all方法而言,
1)、如果传入参数为一个空的可迭代对象,则直接进行resolve
2)、如果参数中有一个promise失败,那么promise.all返回的promise对象失败
3)、任何情况下,promise.all返回的promise的完成状态的结果都是一个数组

Promise.all=function(promises){
  return new Promise((resolve,reject)=>{
    let result=[];
    let index=0;
    let len=promises.length;
    if(len===0){
      resolve(result);
      return;
    }
    for(let i=0;i{
        result[i]=data;
        index++;
        if(index==len) resolve(result);
      }).catch(err=>{
        reject(err);
      })
    }
  })
}

2、实现promise.race

Promsie.race=function(promises){
  return new Promise((resolve,reject)=>{
    let len=promises.length;
    if(len==0)return;
    for(let i=0;i{
        resolve(data);
        return;
      }).catch(err=>{
        reject(err);
        return;
      })
    }
  }) 
}

结束语

       明天写diff算法!!!

\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0

ItVuer - 免责声明 - 关于我们 - 联系我们

本网站信息来源于互联网,如有侵权请联系:561261067@qq.com

桂ICP备16001015号