一、实现原理
1.1 原因及代码段
Generator自动化是通过Thunk函数进行实现,写这篇文章的目的是为了理解通过Thunk实现Generator函数的自动执行。
1.2 代入的业务场景
我们可以带入一个业务场景来帮助我们理解Thunk实现Generator自动执行的好处,业务场景如下:
假设小明今天干了一件事情是:
1、买菜
2、买完菜回家先做饭
3、接着洗菜、切菜
4、然后煮菜
5、煮完菜等待饭好后,吃饭
1.3 实现Generator自动执行需要准备的函数
1.3.1 Thunk函数——通用版
概念:Thunk 函数替换的不是表达式,而是多参数函数,将其替换成单参数的版本,且只接受回调函数作为参数
1. 有且只有一个参数是callback(回调)的函数;
2. 原函数是多参数函数;(如果只有一个参数是没有改造的必要)
3. 通常情况下,callback的第一个参数是error
代码1(通用Thunk):
function Thunk(fn) {
return function(...args) {
return function(callback) {
return fn.call(this,...args,callback)
}
}
}
代码2(使用Thunk,引入代码2只是为了介绍该封装的通用Thunk的使用规则,其它毫无作用):
function delay(time,dealCallback,callback) {
setTimeout(() => {
dealCallback();
callback();
}, time ? time : 1000);
}
const thunkDelay1 = Thunk(delay)(500,() => {
console.log("执行了dealCallback")
});
thunkDelay1(() => {
console.log("通过thunk执行了callback");
})
函数Thunk(使用了函数柯里化实现,函数柯里化可以去了解一下),上图中的thunk实现之后的调用规则实际上是:
① 第一层调用Thunk函数,传入需要执行的函数fn,如:Thunk(delay),此时我们就得到了function(...args) {...}
② 第二层传入需要执行的函数fn中的除callback的参数,如:const thunkDelay1 = Thunk(delay)(500,() => {...})
③ 第三层传入需要执行的callback,如:thunkDelay1(() => {...})
通过该thunk可以执行所有的:拥有多个参数,且只有一个参数为callback的函数。且执行规则必须是上面的①、②、③步骤,此刻看起来好像确实没有什么用,我们需要执行什么函数直接调不就行了?
1.3.2 准备一些需要执行的函数
上文中的delay其实就算是其中一种,不过我们也可以写多个需要执行的函数。这里为什么写的有些需要执行的函数不符合使用Thunk的初衷(拥有多个参数(不止一个),且只有一个callback参数,要是只有一个参数就没必要使用Thunk了):这里是为了再次强调Thunk本身需要满足的条件,因为如果我们改造的函数,像:buyVegetables、cookRice、washVegetables、cookVegetables,如果是只有一个callback参数,那么再后续的执行生成器的函数中(1.3.3)就没必要再使用Thunk修饰了,应该直接使用yield buyVegetables; ...,
// 1、买菜(假设去买菜到买菜到家花了1s钟)
function buyVegetables (callback) {
console.log("小明出去买菜了");
setTimeout(() => {
console.log("买完菜到家了");
callback();
}, 1000)
}
// 2、煮饭
function cookRice(callback) {
console.log("小明去煮饭了");
setTimeout(() => {
console.log("小明已经把米放进电饭煲,按下煮饭开关了");
callack();
}, 1000)
}
// 3、洗菜、切菜
function washVegetables(callback) {
console.log("小明开始洗菜切菜");
setTimeout(() => {
console.log("小明洗菜、切菜");
callback();
}, 1000)
}
// 4、煮菜
function cookVegetables(callback) {
console.log("小明开始煮菜了");
setTimeout(() => {
console.log("美食已经烹饪完");
callback();
})
}
// 5、等待饭熟后吃饭
function eatRice(waitTime, waitHandleThings,callback) {
setTimeout(() => {
console.log("饭熟了");
waitHandleThings();
callback();
}, waitTime ? waitTime : 1000)
}
1.3.3 准备一个执行某个功能的流程生成器:Generator
function *g() {
yield Thunk(buyVegetables)();
yield Thunk(cookRice)();
yield Thunk(washVegetables)();
yield Thunk(cookVegetables)();
yield Thunk(eatRice)(() => {
console.log("等待饭熟的过程中,吃了一个橘子");
});
}
1.3.4 准备一个自动执行生成器的函数:thunkForGenerator
function thunkForGenerator(callback) {
const g = callback();
function next() {
// 调用生成器的next()
let result = g.next();
// 生成器执行完了直接return
if (result.done) {
return;
}
/**
*
* result.value拿到yield后面返回的通过Thunk(fn)(...args)返回的包装的函数func(callback)
* 包装函数最终执行 fn.call(this,...args,callback)
*/
result.value(next);
}
next();
}
1.3.5 所需要准备的函数功能分析
① Thunk函数:它的作用是自动控制Generator函数的流程,接收和交还程序的执行权;
② 需要执行流程的函数:都必须有一个callback参数,用以回调执行Generator中的.next()中的.value获得的函数
③ Generator生成器函数:这个函数主要是为了执行我们所需要执行的流程
④ thunkForGenerator这个函数要求g这个Generator函数里,每个函数里都是Thunk函数(这里我的理解是,Generator函数每一个yield后面执行的函数应该是都有一个callback参数,作为回调执行下一次Generator.next().value() );
二、全部代码
function Thunk(fn) {
return function (...args) {
return function (callback) {
fn.call(this, ...args, callback);
};
};
}
// 1、买菜(假设去买菜到买菜到家花了1s钟)
function buyVegetables(callback) {
console.log("小明出去买菜了");
setTimeout(() => {
console.log("买完菜到家了");
callback();
}, 1000);
}
// 2、煮饭
function cookRice(callback) {
console.log("小明去煮饭了");
setTimeout(() => {
console.log("小明已经把米放进电饭煲,按下煮饭开关了");
callback();
}, 1000);
}
// 3、洗菜、切菜
function washVegetables(callback) {
console.log("小明开始洗菜切菜");
setTimeout(() => {
console.log("小明洗菜、切菜");
callback();
}, 1000);
}
// 4、煮菜
function cookVegetables(callback) {
console.log("小明开始煮菜了");
setTimeout(() => {
console.log("美食已经烹饪完");
callback();
});
}
// 5、等待饭熟后吃饭
function eatRice(waitTime, waitHandleThings, callback) {
setTimeout(
() => {
console.log("饭熟了");
waitHandleThings();
callback();
},
waitTime ? waitTime : 1000
);
}
// 生成器方法
function* g() {
yield Thunk(buyVegetables)();
yield Thunk(cookRice)();
yield Thunk(washVegetables)();
yield Thunk(cookVegetables)();
yield Thunk(eatRice)(500,() => {
console.log("等待饭熟的过程中,吃了一个橘子");
});
}
function thunkForGenerator(callback) {
const g = callback();
function next() {
// 调用生成器的next()
let result = g.next();
// 生成器执行完了直接return
if (result.done) {
return;
}
/**
*
* result.value拿到yield后面返回的通过Thunk(fn)(...args)返回的包装的函数func(callback)
* 包装函数最终执行 fn.call(this,...args,callback)
*/
result.value(next);
}
next();
}
thunkForGenerator(g);
2.1 代码在执行时干了什么事
1. 代码执行过程
执行thunkForGenerator(g):
① const g = callback()将生成器赋值给了g;
② 接着执行了函数next(),函数next中将 g.next()赋值给了result,此时result拿到了:
{
vlaue: Thunk(buyVegetables)(),
done: false
}
等价于
{
value: (callback) => {
buyVegetables.call(this,callback)
},
done: false
}
接着执行result.value(next),实际等价于执行上面的.value方法,顺便将next作为回调函数callback传递进去,执行到回调函数next之后,又会通过g.next()拿到下一个yield后面的函数,如此循环反复,直到yield已经都被执行完,再次通过g.next()拿到的是 :
{
value: undefined,
done: true
}
此时,通过判断:
if (result.done) {
return
}
中断后续的行为,因为已经执行完成了
2.控制权的交还流程如下:文章来源:https://www.toymoban.com/news/detail-843326.html
虽然thunkForGenerator函数将next方法传给了Thunk函数,但该函数不会立即执行,而是传给了delay函数的参数三;
而delay函数的参数三是一个函数,但该函数只会在异步执行完毕后去执行;
也就是说,当第一次执行遍历器的next方法后,控制权从thunkForGenerator函数交给了g函数;
然后g函数会触发一次1000ms延迟的异步(thunkForAsync函数),等异步执行完毕后,g函数执行了thunkForGenerator函数的next方法,将控制权重新还给了他;
然后thunkForGenerator函数发现done为false,于是又执行了遍历器的next方法,将控制权交给了g函数;
g函数执行完代码,通过执行thunkForGenerator函数的next方法,将控制权还了回去;
此时thunkForGenerator函数再次执行遍历器的next,但g函数发现此时没代码可执行了,于是返回结果,done为true;
因为done为true,所以thunkForGenerator函数不再继续下去,也返回了。文章来源地址https://www.toymoban.com/news/detail-843326.html
到了这里,关于Javascript——生成器(Generator)自动执行的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!