Generator-上下文-协程
Generator 与上下文
JavaScript 代码运行时,会产生一个全局的上下文环境(context,又称运行环境),包含了当前所有的变量和对象。然后,执行函数(或块级代码)的时候,又会在当前上下文环境的上层,产生一个函数运行的上下文,变成当前(active)的上下文,由此形成一个上下文环境的堆栈(context stack)。
这个堆栈是“后进先出”的数据结构,最后产生的上下文环境首先执行完成,退出堆栈,然后再执行完成它下层的上下文,直至所有代码执行完成,堆栈清空。
Generator 函数不是这样,它执行产生的上下文环境,一旦遇到yield命令,就会暂时退出堆栈,但是并不消失,里面的所有变量和对象会冻结在当前状态。等到对它执行next命令时,这个上下文环境又会重新加入调用栈,冻结的变量和对象恢复执行。
生成器实现机制——协程
协程是一种比线程更加轻量级的存在,协程处在线程的环境中,一个线程可以存在多个协程,可以将协程理解为线程中的一个个任务。不像进程和线程,协程并不受操作系统的管理,而是被具体的应用程序代码所控制。
个人理解:JS线程遇到Generator函数开了个协程在那里处理,然后外面往后执行,协程内部处理上下文环境的堆栈(context stack),以此来实现中断
协程的运作过程
一个线程一次只能执行一个协程。比如当前执行 A 协程,另外还有一个 B 协程,如果想要执行 B 的任务,就必须在 A 协程中将JS 线程的控制权转交给 B协程,那么现在 B 执行,A 就相当于处于暂停的状态。
function* A() {
console.log("我是A");
yield B(); // A停住,在这里转交线程执行权给B
console.log("结束了");
}
function B() {
console.log("我是B");
return 100;// 返回,并且将线程执行权还给A
}
let gen = A();
gen.next();
gen.next();
// 我是A
// 我是B
// 结束了
A 将执行权交给 B,也就是 A 启动 B,我们也称 A 是 B 的父协程。因此 B 当中最后return 100其实是将 100 传给了父协程。
高性能调度
需要强调的是,对于协程来说,它并不受操作系统的控制,完全由用户自定义切换,因此并没有进程/线程上下文切换的开销,这是高性能的重要原因。
Generator异步流程控制
thunk
thunk 函数。它的核心逻辑是接收一定的参数,生产出定制化的函数,然后使用定制化的函数去完成功能。thunk函数的实现会比单个的判断函数复杂一点点,但就是这一点点的复杂,大大方便了后续的操作
readFileThunk就是一个thunk函数。异步操作核心的一环就是绑定回调函数,而thunk函数可以帮我们做到。首先传入文件名,然后生成一个针对某个文件的定制化函数。这个函数中传入回调,这个回调就会成为异步操作的回调
const readFileThunk = (filename) => {
return (callback) => {
fs.readFile(filename, callback);
}
}
const gen = function* () {
const data1 = yield readFileThunk('001.txt')
console.log(data1.toString())
const data2 = yield readFileThunk('002.txt')
console.log(data2.toString)
}
function run(gen){
const next = (err, data) => {
let res = gen.next(data);
if(res.done) return;
// value是readFileThunk函数
res.value(next);
}
next();
}
let g = gen();
run(g);
Promise
Async await就是这种控制流程