Generator和协程
Generator与协程
什么是协程
协程是一种程序运行的方式,可以理解成“协作的线程”或“协作的函数”。协程既可以用单线程实现,也可以用多线程实现。前者是一种特殊的子例程,后者是一种特殊的线程。
协程与子例程的差异
传统的“子例程”(subroutine)采用堆栈式“后进先出”的执行方式,只有当调用的子函数完全执行完毕,才会结束执行父函数。协程与其不同,多个线程(单线程情况下,即为多个函数)可以并行执行,但是只有一个线程(或函数)处于正在运行的状态,其他线程(或函数)都处于暂停态,线程(或函数)之间可以交换执行权。也就是说,一个线程(或函数)执行到一半,可以暂停使用,将执行权交给另一个线程(或函数),等到稍后收回执行权的时候,再回复执行。这种可以并行执行、交换执行权的线程(或函数),就称为协程。
协程和普通线程的差异
协程适合用于多任务运行的环境。在这个意义上,他与不同的简称很相似,都有自己的执行上下文、可以分享全局变量。它们的不同之处在于,同一时间可以有多个线程处于运行状态,但运行的协程只能有一个,其他协程都处于暂停状态。此外,普通的线程是抢先式的,那个线程优先得到资源,是由运行环境决定,但协程是合作式的,执行权由协程自己分配。
JavaScript是单线程语言,只能保持一个调用栈。引入协程后,每个任务可以保持自己的调用栈。这样做能够在抛出错误的时候,找到原始的调用栈(Genterator函数在执行产生上下文环境时,遇到yield命令就会暂时退出堆栈,并不会消失里面所有的变量和对象都会冻结在当前状态,等到对它执行next命令时,这个上下文环境有重新加入调用栈)。不至于出现像异步操作的回调函数那样,一旦出现错误,原始的调用栈早就结束了。
协程的运行流程大致如下:
第一步,协程A开始执行。
第二步,协程A执行到一半,进入暂停,执行权转移到协程B。
第三步,(一段时间后)协程B交还执行权。
第四步,协程A恢复执行。
Generator函数是ES6对协程的不完全实现。Generator被称为“半协程”,意思是只有Generator函数的调用者,才能将程序的执行权还给Genertor函数。若是完全执行的协程,则人后函数都可以让暂停的协程继续执行。
下面是使用Generator函数实现代码:
function* gen() {
yield 1;
return 2;
}
let g = gen();
console.log(
g.next().value,
g.next().value,
);
上面代码中,第一次执行g.next()时,Generator 函数gen的上下文会加入堆栈,即开始运行gen内部的代码。等遇到yield 1时,gen上下文退出堆栈,内部状态冻结。第二次执行g.next()时,gen上下文重新加入堆栈,变成当前的上下文,重新恢复执行。