理解async
async的执行原理其实就是自动执行generator函数,暂时不考虑genertor的编译步骤(比较复杂)。换言之,不考虑修饰符async、await的实现,只关注async的执行原理。
Generator (生成器)
Generator函数是ES6提供的一种异步编程的解决方案。
首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
官网给出的解释: 使用 function* 语法和一个或多个 yield 表达式以创建一个函数即为生成器,当然它的返回值就是一个迭代器即生成器
本质就是个函数:它不同于普通函数,是可以暂停执行的,所以函数名之前要加星号,以示区别。其实整个 Generator 函数就是一个封装的异步任务,或者说是异步任务的容器。异步操作需要暂停的地方,都用 yield 语句注明。
调用 Generator 函数,会返回一个内部指针(即遍历器)g 。也就是说执行 Generator 函数返回的是指针对象。调用指针 g 的 next 方法,会移动内部指针(即执行异步任务的第一段),指向第一个遇到的 yield 语句;再次调用g的next的方法,内部指针指向下个yield语句。
next() :
每次调用 next 方法,会返回一个对象:
- done 属性是一个布尔值,表示 Generator 函数是否执行完毕
- value 属性是 yield 语句后面表达式的值,表示当前阶段的值
throw():
使用指针对象的 throw 方法抛出的错误,可以被函数体内的 try ... catch 代码块捕获。这意味着,出错的代码与处理错误的代码,实现了时间和空间上的分离,这对于异步编程无疑是很重要的。
function* gen(pramas) {
try {
yield "start form " + pramas;
yield () => { return "doing form " + pramas };
yield { label: "end form " + pramas };
var y = yield x + 2; // 加入测试代码
} catch (error) {
console.log("gen throw " + error);
}
return "finally form " + pramas;
}
let obj = gen('cx');
console.log(obj); // ƒ* gen(pramas)
console.log(obj.next()); // {value: 'start form cx', done: false}
console.log(obj.next()); //{done: false, value: ƒ}
console.log(obj.next()); // {value: {…}, done: false}
console.log(obj.next()); // {value: 'finally form cx', done: true}
console.log(obj.next()); //{value: undefined, done: true}
console.log(obj.throw("出错了"));
// gen throw ReferenceError: x is not defined
// Uncaught 出错了
async函数的实现
示例
const getData = () => new Promise(resolve => setTimeout(() => resolve("data"), 1000))
async function test() {
const data = await getData()
console.log('1: ', data);
const data2 = await getData()
console.log('2: ', data2);
return 'success'
}
// 这样的一个函数 应该再1秒后打印data 再过一秒打印data2且打印success
test().then(res => console.log(res))
如果我们把它用generator函数表达,会是怎么样的呢?
第一次调用
next
,其实只是停留在了yield getData()
这里,data
的值并没有被确定。下一次调用next的时候,传的参数会被作为上一个yield前面接受的值。
const getData = () => new Promise(resolve => setTimeout(() => resolve("data"), 1000))
function* testG() {
// await被编译成了yield
const data1 = yield getData()
console.log('data: ', data1);
const data2 = yield getData()
console.log('data2: ', data2);
return 'success'
}
let gen = testG()
let dataPromise = gen.next().value; // 第一次调用`next`, data1 = undefined
dataPromise.then((value1) => {
let data2Promise = gen.next(value1).value // 再次调用`next`,将参数作为上一个yield的返回值, data1 = value1的值
// 此时就会打印出data;console.log('data: ', data);
data2Promise.then((value2) => {
gen.next(value2) // 再次调用`next`,将参数作为上一个yield的返回值, data2 = value2的值
// 此时就会打印出data2,console.log('data2: ', data2);
})
})
思路
generator函数是不会自动执行的,每一次调用它的next方法,会停留在下一个yield的位置。
利用这个特性,我们只要编写一个自动执行的函数,就可以让这个generator函数完全实现async函数的功能。
function asyncToGenerator(generatorFunc) {
return () => {
// 先调用generator函数 生成迭代器 对应 var gen = testG()
const gen = generatorFunc
// 返回一个promise 因为外部是用.then的方式 或者await的方式去使用这个函数的返回值的
// let test = asyncToGenerator(testG)
// test().then(res => console.log(res))
return new Promise((resolve, reject) => {
// 内部定义一个step自动执行的函数,
// key有next和throw两种取值,分别对应了gen的next和throw方法
// arg参数则是用来把promise resolve出来的值交给下一个yield
function step(key, arg) {
let generatorResult
try {
generatorResult = gen[key](arg)
} catch (error) {
return reject(error)
}
const {value, done} = generatorResult
if (done) { // 执行完毕,直接返回
return resolve(value);
} else { // 执行下一个yield,直接返回
return Promise.resolve(value).then(val => step('next', val), err => step('throw', err))
}
}
// 首次进入会执行一次next(), 此时value = undefined,指针指向第二个yield
step("next");
})
}
}
案例
import asyncToGenerator from "@/kits/UiKit";
// 模拟请求
const getData = () => new Promise(resolve => setTimeout(() => resolve("data"), 1000))
// 原来我们这样写async函数
async function test() {
const data1 = await getData()
console.log("1==>",data1);
const data2 = await getData()
console.log("2==>",data2)
return data2
}
// 用我们实现的async函数
function* testG() { // async函数会被编译成generator函数 (babel会编译成更本质的形态,这里我们直接用generator)
// await被编译成了yield
const data1 = yield getData()
console.log("1==>",data1);
const data2 = yield getData()
console.log("2==>",data2)
return data2
}
//test().then(res=>{
// console.log("finally==>",res);
//})
asyncToGenerator(testG)().then(res => {
console.log("finally==>",res);
})
// 1==> data
// 2==> data
// finally==> data
最后一句
学习心得!若有不正,还望斧正。希望掘友们不要吝啬对我的建议。