Generator 与异步操作 Thunk 函数 koa 中使用 Generator Generator 与异步操作 这一节正式开始讲解Generator如何进行异步操作,以前我们花了好几节的时间各种打基础,现在估计大家也都等急了,好戏马上开始! 本节内容概述 在Genertor中使用thunk函数 挨个读取两个文件的内容 自驱动流程 使用co库 co库和Promise 接下来... 在Genertor中使用thunk函数 这个比较简单了,之前都讲过的,直接看代码即可。代码中表达的意思,是要依次读取两个文件的内容 const readFileThunk = thunkify(fs.readFile) const gen = function* () { const r1 = yield readFileThunk('data1.json') console.log(r1) const r2 = yield readFileThunk('data2.json') console.log(r2) } 挨个读取两个文件的内容 接着以上的代码继续写,注释写的非常详细,大家自己去看,看完自己写代码亲身体验。 const g = gen() // 试着打印 g.next() 这里一定要明白 value 是一个 thunk函数 ,否则下面的代码你都看不懂 // console.log( g.next() ) // g.next() 返回 {{ value: thunk函数, done: false }} // 下一行中,g.next().value 是一个 thunk 函数,它需要一个 callback 函数作为参数传递进去 g.next().value((err, data1) => { // 这里的 data1 获取的就是第一个文件的内容。下一行中,g.next(data1) 可以将数据传递给上面的 r1 变量,此前已经讲过这种参数传递的形式 // 下一行中,g.next(data1).value 又是一个 thunk 函数,它又需要一个 callback 函数作为参数传递进去 g.next(data1).value((err, data2) => { // 这里的 data2 获取的是第二个文件的内容,通过 g.next(data2) 将数据传递个上面的 r2 变量 g.next(data2) }) }) 上面 6 行左右的代码,却用了 6 行左右的注释来解释,可见代码的逻辑并不简单,不过你还是要去尽力理解,否则接下来的内容无法继续。再说,我已经写的那么详细了,你只要照着仔细看肯定能看明白的。 也许上面的代码给你带来的感觉并不好,第一它逻辑复杂,第二它也不是那么易读、简洁呀,用Generator实现异步操作就是这个样子的?———— 当然不是,继续往下看。 自驱动流程 以上代码中,读取两个文件的内容都是手动一行一行写的,而我们接下来要做一个自驱动的流程,定义好Generator的代码之后,就让它自动执行。完整的代码如下所示: // 自动流程管理的函数 function run(generator) { const g = generator() function next(err, data) { const result = g.next(data) // 返回 { value: thunk函数, done: ... } if (result.done) { // result.done 表示是否结束,如果结束了那就 return 作罢 return } result.value(next) // result.value 是一个 thunk 函数,需要一个 callback 函数作为参数,而 next 就是一个 callback 形式的函数 } next() // 手动执行以启动第一次 next } // 定义 Generator const readFileThunk = thunkify(fs.readFile) const gen = function* () { const r1 = yield readFileThunk('data1.json') console.log(r1.toString()) const r2 = yield readFileThunk('data2.json') console.log(r2.toString()) } // 启动执行 run(gen) 其实这段代码和上面的手动编写读取两个文件内容的代码,原理上是一模一样的,只不过这里把流程驱动给封装起来了。我们简单分析一下这段代码 最后一行run(gen)之后,进入run函数内部执行 先const g = generator()创建Generator实例,然后定义一个next方法,并且立即执行next() 注意这个next函数的参数是err, data两个,和我们fs.readFile用到的callback函数形式完全一样 第一次执行next时,会执行const result = g.next(data),而g.next(data)返回的是{ value: thunk函数, done: ... },value是一个thunk函数,done表示是否结束 如果done: true,那就直接return了,否则继续进行 result.value是一个thunk函数,需要接受一个callback函数作为参数传递进去,因此正好把next给传递进去,让next一直被执行下去 大家照着这个过程来捋一捋,不是特别麻烦,然后自己试着写完运行一下,基本就能了解了。 使用co库 刚才我们定义了一个run还是来做自助流程管理,是不是每次使用都得写一遍run函数呢?———— 肯定不是的,直接用大名鼎鼎的co就好了。用Generator的工程师,肯定需要用到co,两者天生一对,难舍难分。 使用之前请安装npm i co --save,然后在文件开头引用const co = require('co')。co到底有多好用,我们将刚才的代码用co重写,就变成了如下代码。非常简洁 // 定义 Generator const readFileThunk = thunkify(fs.readFile) const gen = function* () { const r1 = yield readFileThunk('data1.json') console.log(r1.toString()) const r2 = yield readFileThunk('data2.json') console.log(r2.toString()) } const c = co(gen) 而且const c = co(gen)返回的是一个Promise对象,可以接着这么写 c.then(data => { console.log('结束') }) co库和Promise 刚才提到co()最终返回的是Promise对象,后知后觉,我们已经忘记Promise好久了,现在要重新把它拾起来。如果使用co来处理Generator的话,其实yield后面可以跟thunk函数,也可以跟Promise对象。 thunk函数上文一直在演示,下面演示一下Promise对象的,也权当再回顾一下久别的Promise。其实从形式上和结果上,都跟thunk函数一样。 const readFilePromise = Q.denodeify(fs.readFile) const gen = function* () { const r1 = yield readFilePromise('data1.json') console.log(r1.toString()) const r2 = yield readFilePromise('data2.json') console.log(r2.toString()) } co(gen) 接下来... 经过了前几节的技术积累,我们用一节的时间就讲述了Generator如何进行异步操作。接下来要介绍一个开源社区中比较典型的使用Generator的框架 ———— Koa Thunk 函数 koa 中使用 Generator