最新公告
  • 欢迎您光临网站无忧模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 用转换的思想理解async/await

    正文概述 掘金(风恋猫)   2021-03-27   559

    用转换的思想理解async/await

    前言

    之前对async/await总是一知半解,导致有时候场景比较复杂的时候,自己就蒙了。所以这次积累总结了下。我觉得这次能耐心,安静的看下去是有收获的。如果对你们有帮助的话,还请点赞关注鼓励下,提前感谢了。在说async/await之前,我们首先看看generator。网上经常遇到这句话:async/await是generator的语法糖。这句话怎么理解了?我们一步步看下去就会知道,下面我们先解决与generator相关的几个名词。

    迭代器,迭代器对象,生成器,可迭代的对象

    • 迭代器,迭代器对象

    我理解的迭代器和迭代器对象其实是一样的,因为迭代器本身就是一个对象,下面就以迭代器进行说明。

    用自己的话概述下就是:

    1:迭代器是一个拥有next方法的对象。

    2:这个next返回一个达到迭代协议或者规则的对象,迭代协议或者规则对象其实就是包含value和done两个字段的对象。

    • 生成器
    • 可迭代的对象

    什么是可迭代行为,按照我的理解是:

    1:这个对象具有[Symbol.iterator]属性,并且值是函数。我们要注意不是字符串,用代码展示下更清楚。

    const obj = {
      [Symbol.iterator]: () => {}
    }
    // 不是下面这种
    const obj = {
      "Symbol.iterator": () => {}
    }
    

    2:[Symbol.iterator]这个函数执行后会返回一个迭代器。

    generator几种写法

    为什么要说这个了,主要是因为generator用的比较少,所以有时候在比较慌的情况下,会短路,还是因为没总结,下面全部写一遍。

    • 具名函数
    function* init() {}
    
    • 匿名函数
    function* () {}
    
    • 函数表达式
    const init = function* () {}
    
    • 箭头函数
    // 记住 generator 现在还不能用箭头函数写2021/03/24
    const a = *() => {};
    

    简单的generator用法

    再来说一下generator简单的用法,都是为了说明async/await做铺垫,可以去阮一峰老师的es6课程里面熟悉下。

    const init = function*(x) {
        const y = yield (x + 1);
        const z = yield (y / 3);
        return z;
    }
    a = init(5);
    a.next(); // {value: 6, done: false}
    a.next(); // {value: NaN,done: false}
    a.next(); // {value: NaN,done: true}
    
    const init = function*(x) {
        const y = yield (x + 1);
        const z = yield (y / 3);
        return z;
    }
    a = init(6);
    a.next(6); // {value: 7, done: false}
    a.next(6); // {value: 2,done: false}
    a.next(6); // {value: 6,done: true}
    

    来一个promise版本的--很重要

    const test = function* () {
      const a = yield new Promise((resolve, reject) => {
         setTimeout(() => {
         resolve(3);
    }, 2000);
    });
    return a;
    }
    
    const a = test();
    console.log(a.next()); // {value: Promise, done: false}
    console.log(a.next());
    

    构造一个自执行next函数

    上面展示promise版本的generator,主要是为了说明async/await的原理,后面会再说。现在我们要实现一个自执行next函数,因为async/await就是自执行的。我们先实现一个简单的架子。

    const exec = g => {
        const {value, done} = g.next();
        if (!done) {
          return  exec(g);
    }
    }
    const value = exec(g);
    

    稍微强一点的版本

    上面的实现是有一点不好,主要是为了今后我能知道怎么一步步变化过来的,上面的函数并没有传递每一次的value值,现在我们在改版下。

    const g = generator();
    
    const next = value => {
        const {value, done} = g.next(value);
        
         if (done) {
            return value;
         }else {
           next(value)
        }
    }
    
    next();
    

    这一次的版本是不是好一点,记住这个函数很重要。

    promise版本

    下面为了理解asyc/await我们在来一个promise版本

    const g = generator();
    const next = value => {
       const {value, done} = g.next(value);
    
        if (!done) {
            // value 是一个promise
            value.then(val => {
            next(val); 
          })
        }
    }
    

    开始async/await认知

    在说明async/await的之前我要抛出自己的三个认知,如果能帮助你们更好的理解,可以采纳,反之略过。

    1:async 相当于返回一个promise对象

    2:await 相当于一个yield,并放入generator函数里面

    3:await 后面相当于包一层promise对象,并把值放入resolve。下面我用代码简单描述下

    const test = async function() {
       const a = await 123;
       const b = 5 + a;
       console.log(b);
    }
    // 将上面的代码换成我认知的那样
    const test = function() {
       //  认知1 async 相当于返回的是一个promise
       return new Promise((resolve, reject) => {
              const generator = function*() {
                 // 认知2 await 相当于一个yield,并放入generator函数里面
                // 认知3 await  后面 相当于包一层promise对象,并把值放入resolve
                  const a = yield new Promise((resolve, reject) => {
                       resolve(123);
                    });
                   const b = 5 + a; 
                   console.log(b);
                 }
              // 自执行函数要加上哈
              const g = generator();
              const next = val => {
                  const {value, done} = g.next(val);
                  if (done) {
                        resolve(value);
                  } else {
                    value.then(val => next(val));
                 }
              }
                next()
    })
    }
    

    按照我上面转化之后,其实我理解的async/await就是generator与promise的结合。当然源码肯定不是我这样的。现在我们就可以知道,为什么说async/await是genarator的语法糖了吧。有不对的地方可以指出,一起交流,下面用一个实际点复杂点的例子来验证,巩固下。

    工作中的版本

    先封装一个请求函数

    const getList = async url => {
      return await fetch.get(url);
    }
    

    fetch.get请求函数我们用promise模拟下,让其表现的更复杂

    // 其实我觉得axios.post().then(), 最终应该还是promise的封装
    const request = (url) => {
       return new Promise((resolve, reject) => {
          setTimeout(() => {
             resolve({result: [1, 2, 3], code: 1});
           }, 500); // 模拟请求
    });
    }
    const fetch = {
         // url是虚的不要在意
         get: (url) => {
             // 为什么要用request在封一层,除了可以扩展下,还能让这个例子在复杂下
            // 因为用到了then
             return request(url).then(res => {
                   if (res.code === 1) {
                      return {...res, success: true};
                  }
             });
          }
    }
    

    实际业务代码

    class Test extends React.Components {
        async componentDidMount() {
           const data = await getList(url);
           console.log('我是什么时候执行');
           console.log(data);
        }
    }
    

    上面的例子componentDidMount是一个async/await,而getList还是一个async/await,并且fetch.get的返回是request(url).then()。那么下面的打印“我是什么时候执行”会一直等到data有值了才会执行么?

    答案是肯定的

    用转换的思想理解async/await

    分析

    分析1

    我们先用async/await去分析,其实前面几个内容很好分析componentDidMount是async/await,遇到了getList继续async/await下去。但是很多人可能在fetch.get这里迷糊了。这个函数不是已经return了么,data应该没有值哇。这么想的话首先是async/await没有深入理解然后就是没有对promise进行深入理解,下面按照我上面的三层认知来进行刨析。

    分析2

    2-1

    按照我们上面的分析,将第一个函数改造下

     async componentDidMount() {
           const data = await getList(url);
           console.log('我是什么时候执行');
           console.log(data);
        }
    // 改成如下
    const componentDidMount = function() {
       return new Promise((resolve, reject) => {
              const generator = function*() {
                  const data = yield new Promise((resolve, reject) => {
                       resolve(getList(url));
                    });
                   console.log('我是什么时候执行');
                   console.log(data);
                 }
              // 转化成自执行函数形式,参考上面
              const g = generator();
              const next = val => {
                  const {value, done} = g.next(val);
                  if (done) {
                        resolve(value);
                  } else {
                    value.then(val => next(val));
                 }
              }
                next()
    })
    }
    
    • 我们可以看一下上面改造代码,如果想要console.log('我是什么时候执行')这句执行,是不是就要等到value.then运行,那么我们在看看value是什么?从上面可知value其实就是yield后面的promise对象。
             new Promise((resolve, reject) => {
                       resolve(getList(url));
                    });
    

    那么value.then的执行就取决于resolve(getList(url))

    • 再来分析getList(url),这个函数返回什么。getList也是一个async/await。按照我上面认知1,async返回的是一个promise对象,那么就相当resolve(new Promise())

    • 我强烈建议看一下我之前写的一篇关于promise源码这篇文章。看完之后你就会知道,如果一个promise(称作A)resolve里面还是一个promise(称作B),那么A的执行取决于B什么时候执行resolve。

    • 意思就是说console.log('我是什么时候执行'),这句什么时候执行取决于getList的resolve执行时机

    2-2

    我们来到getList发现其实和componentDidMount一样,我们还是改造下

    const getList = async url => {
      return await fetch.get(url);
    }
    // 改造之后
    getList = function() {
       return new Promise((resolve, reject) => {
              const generator = function*() {
                 yield new Promise((resolve, reject) => {
                       resolve(fetch.get(url));
                    });
             
                 }
              // 转化成自执行函数形式,参考上面
              const g = generator();
              const next = val => {
                  const {value, done} = g.next(val);
                  if (done) {
                        resolve(value);
                  } else {
                    value.then(val => next(val));
                 }
              }
                next()
    })
    }
    
    • 这里要说明一点的是getList什么时候resolve,是取决于这行代码if (done) {resolve(value)} ,想要他触发那么必须就要done是true,必然要把第一个yield执行完。

    • 我们看看第一个yield,还是一个promise对象。要想value.then执行必须先执行resolve(fetch.get(url))。有点熟悉了吧。

    • fetch.get 返回如下:request(url).then()。问题的最关键地方来了,这是一个promise么?如果不是没什么好说的,直接返回。如果是一个promise就需要继续分析下去,因为我们的500毫秒的停顿还没开始了。

    • 了解promise源码的人应该知道,then就是返回一个新promise对象。

    • resolve(fetch.get(url))返回又是一个新的promise,那么resolve的执行是不是需要看fetch.get(url)返回的这个promise对象什么执行resolve。换句话说就是看request(url).then()这个promise什么时候resolve。

    2-3

    再次对promise进行解释。then返回的promise什么执行,其实就是then什么时候执行。放一段我之前写的then的源码。

    if (self.info.status === "pending") {
          self.onFulfilledArr.push((data) => {
            setTimeout(() => {
              try {
                let value = data;
                value = onFulfilled(value);
    
                resolve(value);
              } catch (e) {
                reject(e);
              }
            });
          });
    

    我们会看到,只要then函数(第一个回调)被执行了,那么直接获取到值,然后resolve了。回到request(url).then()这句代码,then什么时候被执行,是不是看request(url)这个promise什么时候resolve,根据代码可以得出他是在500毫秒之后resolve的。可能你现在有点没串起来我下面大概捋一下。

    2-4

    async/await(compontDidMount)----->需要等到getList()这个promise对象resolve才能继续next(),才会去打印----->而getList()的resolve是要等到fetch.get()或者说request(url).then() resolve了才会执行----->而request(url).then()这个promise的resolve需要等到then函数执行----->然后then函数回调执行需要等到request(url)返回的promise对象resolve----->根据代码可知request(url)返回的promise对象在500毫秒之后resolve----->也就是整个代码由于generator函数会一直等待,直到500毫秒之后resolve了才去next。

    总结

    看了上面的总结,你可能对async/await有一个新的认识。网上很多文章是深入源码讲解。但是我这一篇文章是将async/await转化成promise + generator进行说明。当然如果你还想知道generator是怎么实现的,可以在网上搜一下。

    参考文章

    Async / Await / Generator 实现原理


    下载网 » 用转换的思想理解async/await

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元