Open mytac opened 6 years ago
虽然现在异步解决方案都用async/await了,但co是一个小巧而精致的库,我们也可以学习一下他的实现原理。
async/await
co是一个函数库,用于generator的自动执行。
我们先定义一个generator,用来异步读下面几个文件
const gen=function *(){ const b=yield read('b.js') console.log(b.length) const c=yield read('c.js') console.log(c.length) }
其中,我们定义的read异步读取文件的函数为:
function read(file){ fs.readFile(file,'utf8',(err,res)=>{ g.next(res) }) } const g=gen() g.next() //将分别输出两个文件的长度
虽然上面的代码也可以顺利异步执行generator函数,但是需要把next()写到异步函数中, 增加了耦合度,而且还要创建generator的实例,代码很繁琐。
next()
我们使用co重构上面的代码:
首先要改造一下异步函数的结构,co规定所有的异步函数必须是偏函数,称之为thunk函数:
function read(file){ return (fn)=>{ fs.readFile(file,'utf8',fn) } }
之后,只要把generator传到co中就大功告成了
const co=require('co') co(gen)() //分别输出两个文件的长度
function co(fn) { return function(done) { const ctx = this; const gen = fn.call(ctx); let it = null; function _next(err, res) { if(err) res = err; it = gen.next(res); //{value:function(){},done:false} if(!it.done){ it.value(_next); } } _next(); } }
这部分的核心代码为:
function _next(err, res) { if(err) res = err; it = gen.next(res); //{value:function(){},done:false} if(!it.done){ it.value(_next); } } _next();
他的目的是递归调用generator的next(),直到done为true时,停止递归。
在调用第一次next()的之后,it为:
it
it={ value:(fn)=>{fs.readFile(file,'utf8',fn)}, done:false }
在it.done为false的情况下,将_next()这个函数作为实参传到(fn)=>{fs.readFile(file,'utf8',fn)}中。 这时才正式执行读取文件的操作,res为读取的文件内容,之后再次执行_next()。
it.done
false
_next()
(fn)=>{fs.readFile(file,'utf8',fn)}
第二次调用_next()后,将res值塞回gen中,之后执行:
res
gen
const b=res console.log(b.length)
之后往后执行,遇到yield又中断了,再次读取c.js中的内容,之后又进入_next(),再把res值塞回gen中,和上次一样执行下列代码:
yield
c.js
const c=res console.log(c.length)
之后再触发_next()知道done状态为true。
done
true
这里的执行顺序比较乱,强烈建议大家去加断点多跑几遍!
function co(fn) { return function(done) { const ctx = this; const gen = fn.call(ctx); let it = null; function _next(err, res) { if(err) res = err; it = gen.next(res); //{value:function(){},done:false} if(!it.done){ it.value(_next); } } _next(); } } function read(file){ return (fn)=>{ fs.readFile(file,'utf8',fn) } } const gen=function *(){ const b=yield read('error.js') console.log(b.length) const c=yield read('package.json') console.log(c.length) } co(gen)()
虽然现在异步解决方案都用
async/await
了,但co是一个小巧而精致的库,我们也可以学习一下他的实现原理。什么是co
co是一个函数库,用于generator的自动执行。
我们先定义一个generator,用来异步读下面几个文件
其中,我们定义的read异步读取文件的函数为:
co的使用
虽然上面的代码也可以顺利异步执行generator函数,但是需要把
next()
写到异步函数中, 增加了耦合度,而且还要创建generator的实例,代码很繁琐。我们使用co重构上面的代码:
首先要改造一下异步函数的结构,co规定所有的异步函数必须是偏函数,称之为thunk函数:
之后,只要把generator传到co中就大功告成了
实现一个简单的co
这部分的核心代码为:
他的目的是递归调用generator的next(),直到done为true时,停止递归。
在调用第一次
next()
的之后,it
为:在
it.done
为false
的情况下,将_next()
这个函数作为实参传到(fn)=>{fs.readFile(file,'utf8',fn)}
中。 这时才正式执行读取文件的操作,res为读取的文件内容,之后再次执行_next()
。第二次调用
_next()
后,将res
值塞回gen
中,之后执行:之后往后执行,遇到
yield
又中断了,再次读取c.js
中的内容,之后又进入_next()
,再把res
值塞回gen
中,和上次一样执行下列代码:之后再触发
_next()
知道done
状态为true
。全部代码
这里的执行顺序比较乱,强烈建议大家去加断点多跑几遍!
相关链接