continuation 教程: 用 yield 实现协程调度
这是一个 continuation 系列教程:
任务队列
先来定义一个任务队列:
let ready = [];然后定义一个执行函数:
function run(){ while (ready.length > 0) { const k = ready.shift() k(); }}从定义可以看出,任务队列中的元素都是函数,然后在运行函数 run 中,会依次执行队列中的函数。可以这样来使用我们的任务队列:
ready.push( () => console.log(1) );ready.push( () => console.log(2) );ready.push( () => console.log(3) );run();// 1// 2// 3顺序执行
现在我们有这样两个 task 函数,把他们添加到任务队列后,会按照顺序执行并打印出结果,这很好理解,符合我们的直觉:
function taskA(){ console.log("task A0"); console.log("task A1");}function taskB(){ console.log("task B0"); console.log("task B1");}ready.push(taskA);ready.push(taskB);run();// task A0// task A1// task B0// task B1现在的打印顺序是 A0 -> A1 -> B0 -> B1,有没有什么办法,可以改变打印顺序,变为 A0 -> B0 -> A1 -> B1 呢?这里的每一个打印语句都是一个 task,而我们关心的是 task 的执行顺序。
yield
yield 关键字的含义是,保存当前的执行环境,把当前任务放到队列最后面,然后去运行其他的任务。就像是在排队,yield 是一个非常讲礼貌的人,当轮到自己的时候,会自己跑去队伍最后面,继续排队。
很多语言都提供了 yield 关键字,我们现在要做的,是在不使用 yield 关键字的情况下,实现 yield 的语义。可以这样定义 yieldCPS 函数,这个函数干的事情,就相当于 yield 关键字:
function yieldCPS(k){ ready.push(k); // 把当前步骤的执行环境存起来 const next = ready.shift(); // 去执行队列头部的其他任务 next();}yieldCPS( () => console.log("yield cps") );run(); // yield cpsyieldCPS 接受一个函数作为参数,如果我们想在 taskA 里使用 yield 语义来影响 A0 和 A1 的执行顺序,可以这样写:
function taskAYield(yieldFn){ console.log("task yield A0"); yieldFn( () => console.log("task yield A1") );}ready.push( () => taskAYield(yieldCPS) );run();// task yield A0// task yield A1但是你发现了,打印出来的顺序没有变,因为确实不应该变,A1 之后没有其他任务了,排队的时候,yield 定义的任务已经没有谦让的余地。
在 taskB 里也用上 yield 试试:
function taskBYield(yieldFn){ console.log("task yield B0"); yieldFn( () => console.log("task yield B1") );}这个时候再把 taskA 和 taskB 放进任务队列,打印结果的顺序就有变化了:...
剩余内容已隐藏