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类似