美文网首页
小邵教你玩转Generator+co/async await

小邵教你玩转Generator+co/async await

作者: iamswr | 来源:发表于2018-08-20 18:51 被阅读0次

    前言:大家好,我叫邵威儒,大家都喜欢喊我小邵,学的金融专业却凭借兴趣爱好入了程序猿的坑,从大学买的第一本vb和自学vb,我就与编程结下不解之缘,随后自学易语言写游戏辅助、交易软件,至今进入了前端领域,看到不少朋友都写文章分享,自己也弄一个玩玩,以下文章纯属个人理解,便于记录学习,肯定有理解错误或理解不到位的地方,意在站在前辈的肩膀,分享个人对技术的通俗理解,共同成长!

    后续我会陆陆续续更新javascript方面,尽量把javascript这个学习路径体系都写一下
    包括前端所常用的es6、angular、react、vue、nodejs、koa、express、公众号等等
    都会从浅到深,从入门开始逐步写,希望能让大家有所收获,也希望大家关注我~

    源码地址:https://github.com/iamswr/promiseAplus
    在看这篇文章之前可以先看看整个异步的发展 https://juejin.im/post/5b6e5cbf51882519ad61b67e

    Author: 邵威儒
    Email: 166661688@qq.com
    Wechat: 166661688
    github: https://github.com/iamswr/


    主要讲一下Generator和co库、async await,配合使用


    Generator

    Generator 基本使用

    Generator是一个生成器,生成出一个迭代器,主要是用来控制异步流程的,目前在现有的库中还是比较少看到generator的,目前主要使用generator的是redux-saga这个库,koa1.0也是用generator,但是现在都改为async/await。

    generator生成器看起来很像普通函数,但是它是在函数名前加了一个 * 号

    function* say(){  // 在函数名前加 *
        
    }
    

    生成器函数可以暂停,而普通函数则是默认一路到底执行代码,生成器函数在内部碰到yield就可以实现暂停功能,使用next进行迭代

    function* say(){
        yield 1
        yield 2
        yield 3
        return 'end'
    }
    
    // 1.执行这个生成器看起来跟执行普通函数差不多,
    //   但是实际上,执行这个生成器,会返回一个迭代器
    let it = say()  
                    
    // 2.此时it是一个迭代器 iterator,打印输出是 {}
    console.log(it)
    
    let obj1 = it.next()
    // 3. 使用next进行迭代,打印输出 { value: 1, done: false }
    //    可以看出,我们执行say生成器,用it来接收生成器返回的迭代器,
    //    而通过迭代器it执行next(),会返回 { value: 1, done: false }
    //    这里的value代表的是上面yield后的值,依次返回,
    //    done为false,意思是还没结束,后面还有yield或者return,当走到return时
    //    done会变为true,也就是完成了
    console.log(obj1) 
    

    我们这样完整看一下

    function* say() {
      let a = yield 1 // 第一个it.next()时返回
      let b = yield 2 // 第二个it.next()时返回
      let c = yield 3 // 第三个it.next()时返回
      return 'end'    // 第四个it.next()时返回
    }
    
    let it = say()
    let obj1 = it.next()
    console.log(obj1) // { value: 1, done: false }
    let obj2 = it.next()
    console.log(obj2) // { value: 2, done: false }
    let obj3 = it.next()
    console.log(obj3) // { value: 3, done: false }
    let obj4 = it.next()
    console.log(obj4) // { value: 'end', done: true }
    

    迭代器,要我们自行一个一个去迭代,一般我们会通过下面这样进行迭代

    function* say() {
      let a = yield 1 // 第一个it.next()时返回
      let b = yield 2 // 第二个it.next()时返回
      let c = yield 3 // 第三个it.next()时返回
      return 'end'    // 第四个it.next()时返回
    }
    
    let it = say()
    
    function next(){
        let { value,done } = it.next()
        console.log(value) // 依次打印输出 1 2 3 end
        if(!done) next() // 直到迭代完成
    }
    next()
    

    通过上面的例子我们大概明白generator大概是怎么执行的了,
    那么下面我们讲讲,怎么往generator里面放东西。

    image
    function* say() {
      let a = yield 'hello swr1'
      console.log(a)
      let b = yield 'hello swr2'
      console.log(b)
    }
    
    let it = say() // 返回迭代器
    
    // 打印输出 { value: 'hello swr1', done: false }
    // 此时执行迭代器的第一个next,会把上图红色圈的区域执行,并且输出'hello swr1'
    // 此时需要注意的是let a = yield 'hello swr1',并非是把yield 'hello swr1'
    // 赋值给a,那么a是什么时候被赋值呢?我们接着看下面
    console.log(it.next()) 
    
    // 打印输出 我是被传进来的1
    //         { value: 'hello swr2', done: false }
    // 此时我们在next里传参,实际上就是当执行第二个next的时候,
    // 会把上面蓝色圈的区域执行,而这个next的参数,
    // 会被赋值给a,然后执行console.log(a),然后把'hello swr2'输出
    console.log(it.next('我是被传进来的1'))
    
    // 打印输出 我是被传进来的2
    //         { value: undefined, done: true }
    // 此时我们第三次执行next,实际上就是当执行了第三个next的时候,
    // 会把上面黄色圈的区域执行,而这个next的参数,
    // 会被赋值给b,然后执行console.log(b),然后因为没有显式写return xxx,
    // 会被默认返回undefined
    console.log(it.next('我是被传进来的2'))
    

    写到这里,我和大家一样不解,这东西到底有什么用,而且这样一个一个next迭代,也很繁琐,下面就来点实用的,generator可以和promise配合使用。

    generator和Promise、co配合使用

    在讲generator和Promise、co配合使用之前我会讲一下promise化的函数怎么写,因为我们日常开发当中,经常会使用到promise,用一次,就写一大堆代码也不好,那么我们会考虑到写一个通用的函数,可以把回调函数方式的函数,改为promise

    // 假设我们有3个文件,1.txt对应的内容为文本'2.txt'
                       2.txt对应的内容为文本'3.txt'
                       3.txt对应的内容为文本'hello swr'
    
    let fs = require('fs')
    
    // promise化函数
    function promisify(fn){
        return function(...args){
            return new Promise((resolve,reject)=>{
                fn(...args,(err,data)=>{ // node的api第一个参数为err
                    if(err) reject(err)
                    resolve(data)
                })
            })
        }
    }
    
    // 把fs.readFile函数promise化
    let read = promisify(fs.readFile)
    
    read('1.txt','utf8').then((data)=>{
        console.log(data) // 打印输出为 '2.txt'
    })
    

    这样我们就完成了一个通用的promise化函数。

    接下来我们要去了解一下co库

    co库地址:https://github.com/tj/co
    $ npm install co

    // 本处代码promisify源用上面的函数promisify
    // 本处代码read源用上面的函数read
    let co = require('co')
    function* r(){
        let r1 = yield read('1.txt','utf8')
        let r2 = yield read(r1,'utf8')
        let r3 = yield read(r2,'utf8')
        return r3
    }
    
    // 此时我们想取到r3,也就是3.txt里的内容'hello swr'
    // 方法一:
    let it = r()
    let { value,done } = it.next() // value为一个promise对象
                                   // 该对象会把resolve的值传给下一个then
    value.then((data)=>{ // data值为'2.txt'
      let { value,done } = it.next(data)
      return value
    }).then((data)=>{ // data值为'3.txt'
      let { value,done } = it.next(data)
      return value
    }).then((data)=>{ // data值为'hello swr'
      console.log(data) // 打印输出 'hello swr'
    })
    // 这样的写法,反而显得很繁琐复杂了,那么我们下面看下使用generator+co是怎么使用的
    
    // 方法二:
    co(r()).then(data=>{
        console.log(data) // 打印输出 'hello swr'
    })
    // 是不是发现generator+co非常高效?
    // 代码更像同步代码了,那么接下来我们自己实现一个co~
    

    co是如何实现呢?

    function co(it){
        return new Promise((resolve,reject)=>{
            function next(data){
                let { value,done } = it.next(data)
                if(!done){
                    value.then((data)=>{
                        next(data)
                    },reject)
                }else{
                    resolve(value)
                }
            }
            next()
        })
    }
    

    当有两个gennerator函数时,并且其中一个嵌套另外一个

    function* a(){
        yield 1
    }
    
    function* b(){
        // 1. 当我们想在generator b中嵌套generator a时,怎么嵌套呢?
        // 2. yield *a(); ==> yield 1,实际上就是把yield 1 放在这个位置
        //    在生成器函数中使用生成器函数 需要使用 *
        yield *a()
        yield 2
    }
    
    let it = b()
    console.log(it.next())
    

    async await

    // 假设我们有3个文件,1.txt对应的内容为文本'2.txt'
                       2.txt对应的内容为文本'3.txt'
                       3.txt对应的内容为文本'hello swr'
                       
    async function r(){
        try{
            let r1 = await read('1.txt','utf8')
            let r2 = await read(r1,'utf8')
            let r3 = await read(r2,'utf8')
            return r3
        }catch(e){
            console.log(e)
        }
    }
    
    r().then((data)=>{
        console.log(data) // hello swr
    })
    

    有没发现,async + await = generator + co?
    async await解决了异步问题

    1. 可以让代码像同步
    2. 可以使用try catch
    3. 可以使用promise
    4. 如果let r1 = await 后面等待的是promise,那么会把promise的结果赋值给前面的r1,如果let r1 = await 后面等待的是普通值,那么就会把这个普通值赋值给前面的r1
    // 那么我想把上面的3个请求改为并发的呢?
    let arr = Promise.all([read('1.txt','utf8'),read('2.txt','utf8'),read('3.txt','utf8')])
    

    那么async await通过babel编译后是怎样的呢?

    'use strict';
    
    var r = function () {
        var _ref = _asyncToGenerator( // async await被编译成generator /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
            var r1, r2, r3;
            return regeneratorRuntime.wrap(function _callee$(_context) {
                while (1) {
                    switch (_context.prev = _context.next) {
                        case 0:
                            _context.prev = 0;
                            _context.next = 3;
                            return read('100.txt', 'utf8');
    
                        case 3:
                            r1 = _context.sent;
                            _context.next = 6;
                            return read(r1, 'utf8');
    
                        case 6:
                            r2 = _context.sent;
                            _context.next = 9;
                            return read(r2, 'utf8');
    
                        case 9:
                            r3 = _context.sent;
                            return _context.abrupt('return', r3);
    
                        case 13:
                            _context.prev = 13;
                            _context.t0 = _context['catch'](0);
    
                            console.log(_context.t0);
    
                        case 16:
                        case 'end':
                            return _context.stop();
                    }
                }
            }, _callee, this, [[0, 13]]);
        }));
    
        return function r() {
            return _ref.apply(this, arguments);
        };
    }();
    
    function _asyncToGenerator(fn) {
        return function () {
            var gen = fn.apply(this, arguments);
            return new Promise(function (resolve, reject) {
                function step(key, arg) { // 相当于我们上门写的next函数
                    try {
                        var info = gen[key](arg);
                        var value = info.value;
                    } catch (error) {
                        reject(error); return;
                    } if (info.done) {
                        resolve(value);
                    } else {
                        return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); });
                    }
                } return step("next");
            });
        };
    }
    

    我们从callback promise generator async + await基本了解了异步发展的进程了,
    接下来我们用一个例子来贯穿这几个~
    我们有个需求,分别有3个球,一个球执行完动画,才会轮到下一个球执行动画,以此类推

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Document</title>
        <style>
            .ball {
                position: absolute;
                width: 100px;
                height: 100px;
                background: red;
                border-radius: 50%;
                left: 0;
            }
    
            div:nth-child(1) {
                top: 0px
            }
    
            div:nth-child(2) {
                top: 120px
            }
    
            div:nth-child(3) {
                top: 240px
            }
        </style>
    </head>
    
    <body>
        <div>
            <div class="ball"></div>
            <div class="ball"></div>
            <div class="ball"></div>
        </div>
        <script>
            // 4.------------------// async await
            let balls = document.querySelectorAll('.ball');
            function move(ele, distance) {
                return new Promise((resolve, reject) => {
                    let movex = 0;
                    let interval = setInterval(() => {
                        movex++;
                        ele.style.left = movex + 'px';
                        if (movex >= distance) {
                            resolve();
                            clearInterval(interval);
                        }
                    }, 6)
                })
            }
            async function m() {
                await move(balls[0], 500);
                await move(balls[1], 400);
                await move(balls[2], 300)
            }
            m().then(data=>{
                alert('ok');
            });
            // 3.------------------// generator + co
            // let balls = document.querySelectorAll('.ball');
            // function move(ele, distance) {
            //     return new Promise((resolve, reject) => {
            //         let movex = 0;
            //         let interval = setInterval(() => {
            //             movex++;
            //             ele.style.left = movex + 'px';
            //             if (movex >= distance) {
            //                 resolve();
            //                 clearInterval(interval);
            //             }
            //         }, 6)
            //     })
            // }
            // function* m() {
            //     yield move(balls[0], 500);
            //     yield move(balls[1], 400);
            //     yield move(balls[2], 300)
            // }
            // function co(it) {
            //     return new Promise((resolve, reject) => {
            //         function next(data) {
            //             let { value, done } = it.next(data);
            //             if (!done) {
            //                 value.then(data => {
            //                     next(data);
            //                 }, reject);
            //             }else{
            //                 resolve(value);
            //             }
            //         }
            //         next();
            //     })
            // }
            // co(m()).then(data => {
            //     alert('done')
            // })
    
            // 2.------------------// promise
            // let balls = document.querySelectorAll('.ball');
            // function move(ele, distance) {
            //     return new Promise((resolve, reject) => {
            //         let movex = 0;
            //         let interval = setInterval(() => {
            //             movex++;
            //             ele.style.left = movex + 'px';
            //             if (movex >= distance) {
            //                 resolve();
            //                 clearInterval(interval);
            //             }
            //         }, 6)
            //     })
            // }
            // move(balls[0],500).then(data=>{
            //     return move(balls[1],400)
            // }).then(data=>{
            //     return move(balls[2],300)
            // }).then(data=>{
            //     alert('ok');
            // })
            // 1.------------------// callback
            // let balls = document.querySelectorAll('.ball');
            // function move(ele, distance, cb) {
            //     let movex = 0;
            //     let interval = setInterval(()=>{
            //         movex++;
            //         ele.style.left = movex+'px';
            //         if(movex >= distance){
            //             cb();
            //             clearInterval(interval);
            //         }
            //     },6)
            // }
            // move(balls[0], 500, function () {
            //     move(balls[1], 400, function () {
            //         move(balls[2], 300, function () {
            //             alert('成功')
            //         })
            //     })
            // })
        </script>
    </body>
    
    </html>
    

    相关文章

      网友评论

          本文标题:小邵教你玩转Generator+co/async await

          本文链接:https://www.haomeiwen.com/subject/phzliftx.html