记录genrator和async原理的一次学习

114 阅读2分钟

Genrator

阮一峰 generator的使用

手写genrator原理

先来一段genrator函数


const loadFont = () => {
    return new Promise((res) => {
        setTimeout(() => {
            res(1111)
        }, 1000);
    })
}
const loadFont2 = () => {
    return new Promise((res) => {
        setTimeout(() => {
            res(1111)
        }, 0);
    })
}

function* genLoadFont() {
    const result = yield loadFont();
    console.log(result)
    const result1 = yield loadFont2();
    console.log(result1)
    return 'done'
}

var gen = genLoadFont();
gen.next()
gen.next(222)

对它进行babel转换成es5的代码如下 es6转es5 babel使用

  • 新建.babelrc文件
  • npm install -g babel-cli
  • npm install --save-dev babel-preset-es2015 babel-cli
  • babel es6 --out-dir es5 es6\genrator.js -> es5\genratorx.js
'use strict';

var _marked = /*#__PURE__*/regeneratorRuntime.mark(genLoadFont),
    _marked2 = /*#__PURE__*/regeneratorRuntime.mark(myGenerator);

var loadFont = function loadFont() {
    return new Promise(function (res) {
        setTimeout(function () {
            res(1111);
        }, 1000);
    });
};
var loadFont2 = function loadFont2() {
    return new Promise(function (res) {
        setTimeout(function () {
            res(1111);
        }, 0);
    });
};

function genLoadFont() {
    var result, result1;
    return regeneratorRuntime.wrap(function genLoadFont$(_context) {
        while (1) {
            switch (_context.prev = _context.next) {
                case 0:
                    _context.next = 2;
                    return loadFont();

                case 2:
                    result = _context.sent;

                    console.log(result);
                    _context.next = 6;
                    return loadFont2();

                case 6:
                    result1 = _context.sent;

                    console.log(result1);
                    return _context.abrupt('return', 'done');

                case 9:
                case 'end':
                    return _context.stop();
            }
        }
    }, _marked, this);
}

var gen = genLoadFont();
gen.next();
gen.next(222);

regeneratorRuntime.wrap 可以npm i regenerator-runtime 去node_modules里查看

观察genLoadFont函数可以得出以下几点

  • genrator函数写法上采用的是switch case while(1)遇到return会结束循环
  • 存在一个全局_context保存下一次执行指针
  • next函数参数在case2的时候赋值给了result 实现了给上一个yield结果赋值

每一次next其实都是执行一次genrator函数,执行完毕后记录下一次需要执行哪一个switch的case,所以每次执行的时候都相当于从上次执行结果的地方开始执行(所以函数看起来像挂起了),context相当于一个全局变量,记录每次执行的位置以及下一次执行的位置,从而实现函数依次执行

genrator.png

使用js模拟genrator函数执行原理

  class Context {
    constructor() {
      this.prev = 0;
      this.next = 0;
      this.done = false;
      this._send = undefined;
    }
    stop() {
      this.done = true
    }
  }


function gen$(context) {
  var result;
  var result1;
  while (1) {
    switch (context.prev = context.next) {
      case 0:
        context.next = 2;
        return 'result1';

      case 2:
        result = context._send
        context.next = 4;
        console.log(context._send + 'result2')
        return 'result2';

      case 4:
        result1 = context._send
        context.next = 6;
        return 'result3';

      case 6:
        context.stop();
        return undefined
    }
  }
}

let foo = function () {
  const context = new Context();
  return {
    next: function (val) {
      context._send = val
      value = gen$(context);
      console.log(context._send,1111)
      done = context.done
      return {
        value,
        done
      }
    }
  }
}
const bar = foo();
bar.next();
bar.next(222);
bar.next(444);
bar.next(444);
bar.next(444);
const res = bar.next(444);
console.log(res,'res')

async/await

async/await是genrator的语法糖,基于以上三点对genrator进行封装就是async/await的实现原理

相较于genrator,async有以下特点

  • async 函数的返回值是一个promise对象
  • await 后面跟的是一个promise resolve/reject的值
  • 自带执行器 不需要像genrator函数手动的next执行

在then(不一定是promise,也可以是thunk函数)里面交出执行权,执行下一个协程(函数),在reject的时候throw出错误 方便外部try catch捕获 。换句话说每一个函数执行完毕之后再去next从而保证函数的执行顺序,代码看起来跟同步代码一样。

es6-协程

使用then和genrator函数模拟async、await

function* myGenerator() {
    yield Promise.resolve(1);
    yield Promise.resolve(2);
    yield Promise.resolve(3);
    return 44
}

function asyncFunc(genFunc) {
  // async 函数的返回值是一个promise对象
    return new Promise((resolve, reject) => {
        const gen = genFunc();
        function step(val) {
            console.log(val, 'val--')
            let result;
            try {
                result = gen.next(val)
            } catch (error) {
                return reject(error)
            }
            if (result.done) {
                return resolve(result.value)
            }
           // await 后面跟的是一个promise resolve/reject的值
            Promise.resolve(result.value).then((res) => {
              // 自带执行器 不需要像genrator函数手动的next执行  
              step(res)
            }, (err) => {
                gen.throw(err)
            })
        }

        step();
    })
}
asyncFunc(myGenerator).then(val=>{
    console.log(val)
})