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相当于一个全局变量,记录每次执行的位置以及下一次执行的位置,从而实现函数依次执行
使用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从而保证函数的执行顺序,代码看起来跟同步代码一样。
使用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)
})