6.aync和await的原理及实现

async和await的基本使用

https://editor.csdn.net/md/?articleId=116918202

generator函数

generator 返回一个遍历器对象

function* read () {
    console.log(1)
    yield 1
    console.log(2)
    yield 2
    console.log(3)
    yield 3
}

let it = read()

it.next()
it.next()
it.next()

generator函数能够分步执行,实际上是利用switch的原理,每一次执行改变switch里面的条件,使之走向不同的分支

  • 利用babel将上述代码转换一下,可以看到:
"use strict";

var _marked = /*#__PURE__*/regeneratorRuntime.mark(read);

function read() {
  return regeneratorRuntime.wrap(function read$(_context) {
    while (1) {
      switch (_context.prev = _context.next) {
        case 0:
          console.log(1);
          _context.next = 3;
          return 1;

        case 3:
          console.log(2);
          _context.next = 6;
          return 2;

        case 6:
          console.log(3);
          _context.next = 9;
          return 3;

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

var it = read();
it.next();
it.next();
it.next();
  • js将read* 函数翻译成了 read$ 函数,里面实质就是一个switch选择语句
  • regeneratorRuntime是一个类,里面有一个wrap方法,主要用来处理指针问题,即switch中的选择条件,使每一次next,指针都指向下一个case 语句,并且返回一个遍历器

我们可以推测出

  • wrap函数传入一个函数参数read$,且这个函数的参数是一个上下文,里面记录了switch应走哪一条语句,并且执行完每一个case后,上下文内容会改变,以便下一次next,再次执行下一条case

根据思路,我们可以实现这个类

const regeneratorRuntime = {
    mark (genFn) {
        return genFn
    },
    wrap (iteratorFn) {
        //初始化一个上下文
        const context = {
            next: 0,
            done: false,//表示是否执行完毕
            stop () {
                this.done = true
            }
        }
        let it = {}
        it.next = function (v) {
            let value = iteratorFn(context)
            return {
                value,
                done: context.done
            }
        }
        return it
    }


}

可以看到:

  • 每次调用next,会有返回值,返回值是switch语句返回的结果,即yield后面语句返回的结果

next既然是一个函数,就可以传递参数,我们执行这行代码:

function* read () {
    var a = yield 1;
    console.log('1', a)
    var b = yield 2;
    console.log('2', b);
    var c = yield 3;
    console.log('3', c)

}

let it = read();
it.next('sx');
it.next('sx');
it.next('sx');

通过babel进行还原源码:

"use strict";

var _marked = /*#__PURE__*/regeneratorRuntime.mark(read);

function read() {
  var a, b, c;
  return regeneratorRuntime.wrap(function read$(_context) {
    while (1) {
      switch (_context.prev = _context.next) {
        case 0:
          _context.next = 2;
          return 1;

        case 2:
          a = _context.sent;
          console.log('1', a);
          _context.next = 6;
          return 2;

        case 6:
          b = _context.sent;
          console.log('2', b);
          _context.next = 10;
          return 3;

        case 10:
          c = _context.sent;
          console.log('3', c);

        case 12:
        case "end":
          return _context.stop();
      }
    }
  }, _marked);
}

var it = read();
it.next('sx');
it.next('sx');
it.next('sx');
  • 可以看到,next函数可以传递参数,且第一次调用next,参数被赋值给context.sent
  • 第一次case没有被赋值回a,反而是第二个case才给a赋值
  • 说明调用后一次next,会获得前一个yield的

wrap函数相应修改:

const regeneratorRuntime = {
    mark (genFn) {
        return genFn;
    },
    wrap (iteratorFn) {
        // 初始化一个上下文
        const context = {
            //初始指针
            next: 0,
            done: false,//表示是否执行结束
            stop () {
                this.done = true
            }
        }
        it = {}
        it.next = function (v) {
            context.sent = v
            let value = iteratorFn(context);
            return {
                value,
                done: context.done
            }
        }
        return it
    }
}

结果:

function* read () {
    console.log(1)
    var a = yield 1
    console.log('a:', a)
    var b = yield 2
    console.log('b:', b)
    yield 3
}

let it = read()

it.next('sx1') //第一次无输出
it.next('sx2') //第二次输出 a:sx2
it.next('sx3')

总结:

next函数返回一个对象,包括yield语句的执行结果value和context的结束标识done
下一个next的参数会被上一个yield的接收值接收

利用这个特性,我们可以进行文件的读写:

  • 创建两个txt:a.txt,b.txt
  • a里面存储b的路径
  • 读取a,获取其值,然后利用这个值,读取b
  • 实现如下:
const fs = require("fs")
const util = require("util")
const readFile = util.promisify(fs.readFile)

function* read () {
    let data = yield readFile('./source/a.txt', 'utf-8')
    let content = yield readFile(data, 'utf-8')
    console.log(content)
}

let it = read();
// 获取第一次读取文件的value 
let { value } = it.next();
//这里的value 是一个promise,我们要获取其值传给data
value.then(data => {
    let { value: v } = it.next(data)
    //v为第二次读取文件的返回值
    v.then(d => {
        it.next(d)
    })
})

可以看到,read函数中,我们基本将代码变成了同步的形式,只是下面的遍历器的代码,读取文件依然是回调嵌套,这显然不是我们想要的,那么如何将下面的回调嵌套改成同步的形式呢?co库可以解决
npm install co

const fs = require("fs")
const util = require("util")
const { co } = require("co")
const readFile = util.promisify(fs.readFile)

function* read () {
    let data = yield readFile('./source/a.txt', 'utf-8')
    let content = yield readFile(data, 'utf-8')
    return content
}
co(read()).then(data => {
    console.log(data)
}).catch(err => {
    console.log(err)
})

可以看到,co只需要调用一下,传入read函数,就能拿到最后一next得接收值,不用嵌套接收每一次yield的接受值,很方便,代码也基本类似于同步,那么它的原理是什么呢?

  • co返回一个promise,里面存储了最后一次yield的返回值
  • yield是逐步执行的,上一个Yield的接收值变成下一个yield的参数
    我们可以简单模拟一个co库:
function co (it) {
    return new Promise((resolve, reject) => {
        function next (data) {
            let { value, done } = it.next(data);
            //如果执行结束,将结果存储
            if (done) {
                resolve(value)
            }
            // 如果没有,递归调用
            else {
                Promise.resolve(value).then(next, reject)
            }
        }
        next()
    })
}

可以看到,利用co+generator基本实现了代码的串行运行,而async和await就是运用了这个原理,可以说async和await只是co+generator的语法糖,我们把上述代码的* 换成async,把yield换成await,结果依然可以运行:

async function read () {
    let data = await readFile("./source/a.txt", 'utf-8')
    let content = await readFile(data, 'utf-8')
    return content
}

let ans = read()
ans.then(data => {
    console.log(data)
})

上述代码在babel中转换,可以看到其原理基本和generator+co类似

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

问也去

创作不易,感谢支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值