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

前言:你們好,我叫邵威儒,你們都喜歡喊我小邵,學的金融專業卻憑藉興趣愛好入了程序猿的坑,從大學買的第一本vb和自學vb,我就與編程結下不解之緣,隨後自學易語言寫遊戲輔助、交易軟件,至今進入了前端領域,看到很多朋友都寫文章分享,本身也弄一個玩玩,如下文章純屬我的理解,便於記錄學習,確定有理解錯誤或理解不到位的地方,意在站在前輩的肩膀,分享我的對技術的通俗理解,共同成長!javascript

後續我會陸陸續續更新javascript方面,儘可能把javascript這個學習路徑體系都寫一下
包括前端所經常使用的es六、angular、react、vue、nodejs、koa、express、公衆號等等
都會從淺到深,從入門開始逐步寫,但願能讓你們有所收穫,也但願你們關注我~html

源碼地址:github.com/iamswr/prom…
在看這篇文章以前能夠先看看整個異步的發展 juejin.im/post/5b6e5c…前端

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


主要講一下Generator和co庫、async await,配合使用java


Generator

Generator 基本使用

Generator是一個生成器,生成出一個迭代器,主要是用來控制異步流程的,目前在現有的庫中仍是比較少看到generator的,目前主要使用generator的是redux-saga這個庫,koa1.0也是用generator,可是如今都改成async/await。node

generator生成器看起來很像普通函數,可是它是在函數名前加了一個 * 號react

function* say(){  // 在函數名前加 *
    
}
複製代碼

生成器函數能夠暫停,而普通函數則是默認一路到底執行代碼,生成器函數在內部碰到yield就能夠實現暫停功能,使用next進行迭代git

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後的值,依次返回,
//    donefalse,意思是還沒結束,後面還有yield或者return,當走到return時
//    done會變爲true,也就是完成了
console.log(obj1) 
複製代碼

咱們這樣完整看一下es6

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 }
複製代碼

迭代器,要咱們自行一個一個去迭代,通常咱們會經過下面這樣進行迭代github

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裏面放東西。

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庫地址: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>
複製代碼
相關文章
相關標籤/搜索