js 手撕Promise2 实现静态方法 手撕async await generator

Posted lin-fighting

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了js 手撕Promise2 实现静态方法 手撕async await generator相关的知识,希望对你有一定的参考价值。

实现静态方法

Promise.resolve

all finally

Promise.all方法接收一个p romise的iterable类型,并且只返回一个Promise实例。如果全都成功它们的值就作为then的第一个参数的入参,为数组[val1,val2,],但是如果传入的 promise 中有一个失败(rejected),Promise.all 异步地将失败的那个结果给失败状态的回调函数,而不管其它 promise 是否完成。
实现:

 static all(promiseArr) 
    return new Promise((resolve, reject) => 
      let result = [];
      let timers = 0;
      const processSuccess = (index, val) => 
        result[index] = val;
        if (++timers === promiseArr.length) 
          resolve(result);
        
      ;
      promiseArr.forEach((item, index) => 
        if (item instanceof Promise) 
          item.then(
            (data) => 
              processSuccess(index, data);
            ,
            //任何一个promise失败了,直接reject
            reject
          );
         else 
          processSuccess(index, item);
        
      );
    );
  

思路就是直接for循环,先同步的走完所有,然后再判断是不是promise,再判断成功还是失败,只要一个失败,就reject。统计成功的个数以及对应的位置,即可实现。

finally的实现

无论成功失败,finally都会执行,而且resolve和reject的结果回贯穿过去往下面传。
但是finally有个特点,如果放在中间,当finally返回promise,程序会等待这个promise执行完毕再向下走。
当返回的promise是fuillied的时候,没有任何影响。

但是,当返回的promise是reject的时候,这时候会拦截前面的resolve,直接用reject去替代,就本来是resolve(3)的,直接变成reject(222)被捕获。所以返回的promise如果是reject就需要注意下。

实现:
思路:借助.then,因为then会返回新的promise。然后在then里面做处理。无论失败还是成功都调用finally传入的函数。

 finally(cb)
    return this.then(data=>
      //这里返回一个新的promise,作为下一个then的结果。注意这里向下传递的是data,而不是cb的resolve值。
      return Promise.resolve(cb()).then(()=>data)
    ,err=>
      //注意这里返回一个Pormise,然后cb如果失败的话,不会走then,而是作为下一个catch的结果。
      //但是cb成功的话是不会影响原本拒绝的效果,所以cb成功走了.then,在then中要抛出错误err,给下一个Catch,这样就不会影响整个流程。
      return Promise.resolve(cb()).then(()=>throw err)
    )
  

这里主要比较绕,Promise.reoslve是用来等待cb的执行的,如果cb中有异步操作,因为return 一个promise。,所以会等待这个promise的执行。然后原本结果是失败的话有两个处理方法,如果原本失败,但是cb返回的promise成功了,那么会走then。直接抛出原本的错误,不能影响。如果cb返回的promise失败了。就会替代原本的错误,不会走then,而是直接走到下一个catch。

race的实现

只要有一个成功或者失败就立马返回。

 static race(promiseArr) 
    return new Promise((resolve, reject) => 
      let isFirst = true;
      promiseArr.forEach((item, index) => 
        if (item instanceof Promise) 
          //谁先成功就调用谁,一旦状态改变再调用也无效。
          item.then(resolve, reject);
         else 
          resolve(item);
        
      );
    );
  

ract的思路就是直接遍历,谁先成功就调用resolve或者reject。状态一改变后续再调用就无效了。

实现promisify,promisefyAll

将原本回调函数的方法转为promise。
一般像readFile的方法都是通过回调函数获取值的,一般是最后一个参数,参数一般是err和data,err在第一位,err-first原则,基于这点就可以处理了。

function promisify(cb) 
  return function (...args) 
    return new Promise((resolve, reject) => 
      cb(...args, (err, data) => 
        if (err) 
          return reject(err);
        
        resolve(data);
      );
    );
  ;


通过对最后一个参数的处理,如果失败就reject。如果成功就resolve。

实现promisifyAll,将一个对象中所有的通过回调函数的写法改成promise

function promisifyAll(obj) 
  let o = ;
  for (let key in obj) 
    if (typeof obj[key] === "function") 
      o[`$keyPromise`] = promisify(obj[key]);
    
  
  return o


遍历对象,借助promisefiy将其全部转化。

async await generator

  • 一开始的异步通过回调函数解决,但是容易造成回调地狱。
  • 而Promise只是优化了一下,并没有完全结果。
  • gerarator,可以把函数的执行权交出去,执行,中断的形式。
  • 终极解决方案: async await,基于generator的语法糖。是异步的最终解决办法。

generator是生成器,他执行之后返回一个迭代器。具体用法可以看mdn。

借助babel可以看到,核心就是靠一个while(1)的死循环加上switch case实现的。
我们可以自己实现一个这种函数。


这里实现regeneratorRuntime对象。

let regeneratorRuntime = 
  mark(genFn) 
    return genFn;
  ,
  wrap(iteratorFn) 
    const context = 
      prev: 0,
      sent: undefined,
      now: undefined,
      next: 0,
      done: false, //表示迭代器是否执行
      stop() 
        this.done = true;
      ,
    ;
    let it = ;
    it.next = function (val) 
      //上一次的next的参数作为上一次yield返回的值。
      context.sent = context.now;
      context.now = val;
      return 
        value: iteratorFn(context),
        done: context.done,
      ;
    ;

    return it;
  ,
;


实现完毕。思路就是通过外部状态context控制开关,利用switch case匹配对应的值在去运行,每次调用Next就运行函数走swtich case流程,去赋值给外部状态context,然后再返回。然后状态变更,下次在调用next的时候就会继续往下走。

自动执行迭代器


可以通过while自动执行迭代器。

我们要拿到promise返回的data还需要通过下列一系列操作才能实现,虽说解决了回调地狱的问题,但是写法太过复杂。有一个库co,可以帮助我们直接获取生成器返回的值。
他接受一个迭代器,我们可以简单实现下co。

co的实现

思路:返回一个promise,然后递归遍历执行完接受的迭代器入参,再resolve出去即可。

function co(it) 
  return new Promise((resolve, reject) => 
    //异步的迭代,只能使用next
    function next(data) 
      let  value, done  = it.next(data);
      if (done) 
        //迭代完毕,直接拿到最后的值resolve
        reject(value);
       else 
        //value不是promise也要变成Promise,如果失败了也要reject
        Promise.resolve(value).then(next, reject);
      
    
    //启动generator
    next();
  );

因为Promise属于异步,异步的迭代只能使用递归,所以这里需要判断如果生成器还没结束,就要继续调用next去走。如果失败则直接reject。

但是这样还需要引入co库。所以更简单的方法又诞生了,async+await

async await


简单的用法,async会将该函数包裹一层,使其返回一个promise,即使返回的是普通值rteturn 1,也会被包裹成类似于return Promise.resolve(1)这样。await表示在此处等待异步执行完毕再接着执行,即使后面跟着的不是promise,也会当作普通代码运行下去。
但其实async+await就是generator+co的语法糖。
可以看看babel的编译

有点多但可以看看关键实现:

可以看到第二个函数就是类似于co的实现,接受迭代器,然后递归调用next。跟成功就resolve,否则就继续调用next。

总结:

  • generator生成器函数,本质就是通过全局状态context,还有一个死循环的switch case,每次Next一下,状态就往下变化。直到最后done变为true就一直是true了。而generator可以实现解决回调地狱,但是写法稍微复杂,co的出现让其写法更加简单,
  • co的实现就是通过接受迭代器,返回一个promise,然后通过递归next一直调用it.next(val)直到done为true,再resolve出来。
  • 最后es8出现的ascync+await就是异步的最终解决办法,其实现思路就跟generator+co一样的套路,只不过他是getnerator+co的语法糖。

以上是关于js 手撕Promise2 实现静态方法 手撕async await generator的主要内容,如果未能解决你的问题,请参考以下文章

js一些数组对象方法原理实现(手撕map,filter,every等)

来,带你手撕一个AOP

JavaScript手撕bind方法?

[ 数据结构 -- 手撕排序算法第六篇 ] 归并排序(下)-- 非递归方法实现

手撕jquery之core.js分析

C语言手撕通讯录(静态动态双版本)——万字实战详解