协程,英文名coroutine,是一种执行过程可以被暂停和恢复的方法。各个协程之间相互协作完成一个任务。
让我们来看一个关于发挥协程作用的例子。假定我们有一个生产者和消费者的关系,生产者创建物品并将物品添加到一个队列,消费者从队列中取出物品并使用该物品。为了提高效率,生产者会一直创建并添加物品,直到队列满为止,队列满时通知运行环境调用消费者;消费者会一直取出并使用物品,直到队列空为止,队列空时通知运行环境调用生产者。下面是使用协程实现这个关系的伪代码:
var q := new queue coroutine produce loop while q is not full create some new items add the items to q yield to consume coroutine consume loop while q is not empty remove some items from q use the items yield to produce
在这段代码中,produce和consume不是普通的方法,而是由关键字coroutine被定义成为协程。当运行环境执行produce时,produce会在队列满时通过yield主动放弃执行权,并告知运行环境去调用consume协程。同样,consume协程在队列空时也会通过yield主动放弃执行权,并告知运行环境去调用produce协程。
在介绍什么是协程之后,接下来我将讲协程在ES6中的实现方式。ES6提供了一种新的方法名叫Generator。Generator的执行过程可以被暂停和恢复,所以它被认为是ES6中的协程,但严格地说,Generator只是半协程(semi-coroutine),因为虽然它可以主动放弃执行权,但是它并没有告知运行环境,下一步哪个协程会被调用。当一个Generator被调用时,它的代码并不会被执行,调用者得到的是它的观察者(Observer)。调用者通过调用这个观察者的方法,比如next方法,来执行Generator的代码。
下面是通过Generator实现上述生产者和消费者关系的ES6代码:
1 const Q = []; 2 const Q_LEN = 10; 3 4 function* produce() { 5 while (Q.length < Q_LEN) { 6 const item = Date.now(); 7 Q.push(item); 8 console.log(`Item ${item} is produced`); 9 10 if (Q.length === Q_LEN) { 11 yield; 12 } 13 } 14 } 15 16 function* consume() { 17 while (Q.length > 0) { 18 const item = Q.pop(); 19 console.log(`Item ${item} is consumed`); 20 21 if (Q.length === 0) { 22 yield; 23 } 24 } 25 } 26 27 function bootstrap() { 28 const producer = produce(); 29 const consumer = consume(); 30 31 while(true) { 32 producer.next(); 33 consumer.next(); 34 } 35 } 36 37 bootstrap();
在上面代码中,produce和consume是两个协程。bootstrap方法是这两个协程的调用者,它首先获取produce和consume协程的观察者,然后循环调用观察者的next方法,从而使得生产者和消费者的关系持续运行。在循环过程中,如果produce检测队列已满,它就主动放弃执行权从而被暂停,consume将获得执行权,如果consume检测队列已空,它就主动放弃执行权从而被暂停,produce将重新获得执行权。
好了,上文简单介绍了什么是协程及其在ES6中的实现方式,希望它能对在理解协程和在ES6中使用协程产生疑问的人有所帮助,谢谢。
参考
- https://en.wikipedia.org/wiki/Coroutine