在 ES6 出现之前,基本都是各式各样类似
的解决方案来处理异步操作的代码逻辑,但是 ES6 的
- Promise
却给异步操作又提供了新的思路,马上就有人给出了如何用
- Generator
来更加优雅的处理异步操作。
- Generator
简介
- Generator
最终如何处理异步操作
- Generator
简介
- Generator
先来一段最基础的
代码
- Generator
- function* Hello() {
- yield 100
- yield (function () {return 200})()
- return 300
- }
- var h = Hello()
- console.log(typeof h) // object
- console.log(h.next()) // { value: 100, done: false }
- console.log(h.next()) // { value: 200, done: false }
- console.log(h.next()) // { value: 300, done: true }
- console.log(h.next()) // { value: undefined, done: true }
在 nodejs 环境执行这段代码,打印出来的数据都在代码注释中了,也可以自己去试试。将这段代码简单分析一下吧
时,需要使用
- Generator
,其他的和定义函数一样。内部使用
- function*
,至于
- yield
的用处以后再说
- yield
生成一个
- var h = Hello()
对象,经验验证
- Generator
发现不是普通的函数
- typeof h
之后,
- Hello()
内部的代码不会立即执行,而是出于一个暂停状态
- Hello
时,会激活刚才的暂停状态,开始执行
- h.next()
内部的语句,但是,直到遇到
- Hello
语句。一旦遇到
- yield
语句时,它就会将
- yield
后面的表达式执行,并返回执行的结果,然后又立即进入暂停状态。
- yield
打印出来的是
- console.log(h.next())
,
- { value: 100, done: false }
是第一个
- value
返回的值,
- yield
表示目前处于暂停状态,尚未执行结束,还可以再继续往下执行。
- done: false
和第一个一样,不在赘述。此时会执行完第二个
- h.next()
后面的表达式并返回结果,然后再次进入暂停状态
- yield
时,程序会打破暂停状态,继续往下执行,但是遇到的不是
- h.next()
而是
- yield
。这就预示着,即将执行结束了。因此最后返回的是
- return
,
- { value: 300, done: true }
表示执行结束,无法再继续往下执行了。
- done: true
时,就只能得到
- h.next()
,因为已经结束,没有返回值了。
- { value: undefined, done: true }
一口气分析下来,发现并不是那么简单,虽然这只是一个最最简单的
入门代码 ———— 可见
- Generator
的学习成本多高 ———— 但是一旦学会,那将受用无穷!别着急,跟着我的节奏慢慢来,一行一行代码看,你会很快深入了解
- Generator
- Genarator
但是,你要详细看一下上面的所有步骤,争取把我写的每一步都搞明白。如果搞不明白细节,至少要明白以下几个要点:
不是函数,不是函数,不是函数
- Generator
不会立即出发执行,而是一上来就暂停
- Hello()
都会打破暂停状态去执行,直到遇到下一个
- h.next()
或者
- yield
- return
时,会执行
- yield
后面的表达式,并返回执行之后的值,然后再次进入暂停状态,此时
- yeild
。
- done: false
时,会返回值,执行结束,即
- return
- done: true
的返回值永远都是
- h.next()
的形式
- {value: ... , done: ...}
最终如何处理异步操作
- Generator
上面只是一个最基本最简单的介绍,但是我们看不到任何与异步操作相关的事情,那我们接下来就先展示一下最终我们将使用
如何做异步操作。
- Generator
之前讲解
时候,依次读取多个文件,我们是这么操作的(看不明白的需要回炉重造哈),主要是使用
- Promise
做链式操作。
- then
- readFilePromise('some1.json').then(data => {
- console.log(data) // 打印第 1 个文件内容
- return readFilePromise('some2.json')
- }).then(data => {
- console.log(data) // 打印第 2 个文件内容
- return readFilePromise('some3.json')
- }).then(data => {
- console.log(data) // 打印第 3 个文件内容
- return readFilePromise('some4.json')
- }).then(data=> {
- console.log(data) // 打印第 4 个文件内容
- })
而如果学会
那么读取多个文件就是如下这样写。先不要管如何实现的,光看一看代码,你就能比较出哪个更加简洁、更加易读、更加所谓的优雅!
- Generator
- co(function* () {
- const r1 = yield readFilePromise('some1.json')
- console.log(r1) // 打印第 1 个文件内容
- const r2 = yield readFilePromise('some2.json')
- console.log(r2) // 打印第 2 个文件内容
- const r3 = yield readFilePromise('some3.json')
- console.log(r3) // 打印第 3 个文件内容
- const r4 = yield readFilePromise('some4.json')
- console.log(r4) // 打印第 4 个文件内容
- })
不过,要学到这一步,还需要很长的路要走。不过不要惊慌,也不要请如来佛祖,跟着我的节奏来,认真看,一天包教包会是没问题的!
接下来我们不会立刻讲解如何使用
做异步操作,而是看一看
- Generator
是一个什么东西!说来话长,这要从 ES6 的另一个概念
- Generator
说起。
- Iterator
ES6 中引入了很多此前没有但是却非常重要的概念,
就是其中一个。
- Iterator
对象是一个指针对象,实现类似于单项链表的数据结构,通过
- Iterator
将指针指向下一个节点 ———— 这里也就是先简单做一个概念性的介绍,后面将通过实例为大家演示。
- next()
本节演示的代码可参考
数据类型
- Symbol
属性的数据类型
- [Symbol.iterator]
对象
- Iterator
返回的也是
- Generator
对象
- Iterator
数据类型
- Symbol
是一个特殊的数据类型,和
- Symbol
- number
等并列,详细的教程可参考。先看两句程序
- string
- console.log(Array.prototype.slice) // [Function: slice]
- console.log(Array.prototype[Symbol.iterator]) // [Function: values]
数组的
属性大家都比较熟悉了,就是一个函数,可以通过
- slice
得到。这里的
- Array.prototype.slice
是一个字符串,但是我们获取
- slice
可以得到一个函数,只不过这里的
- Array.prototype[Symbol.iterator]
是
- [Symbol.iterator]
数据类型,不是字符串。但是没关系,
- Symbol
数据类型也可以作为对象属性的
- Symbol
。如下:
- key
- var obj = {}
- obj.a = 100
- obj[Symbol.iterator] = 200
- console.log(obj) // {a: 100, Symbol(Symbol.iterator): 200}
在此小节中,你只需要知道
是一个特殊的数据类型
- [Symbol.iterator]
类型,但是也可以像
- Symbol
- number
类型一样,作为对象的属性
- string
来使用
- key
属性的数据类型
- [Symbol.iterator]
在 ES6 中,原生具有
属性数据类型有:数组、某些类似数组的对象(如
- [Symbol.iterator]
、
- arguments
)、
- NodeList
和
- Set
。其中,
- Map
和
- Set
也是 ES6 中新增的数据类型。
- Map
- // 数组
- console.log([1, 2, 3][Symbol.iterator]) // function values() { [native code] }
- // 某些类似数组的对象,NoeList
- console.log(document.getElementsByTagName('div')[Symbol.iterator]) // function values() { [native code] }
原生具有
属性数据类型有一个特点,就是可以使用
- [Symbol.iterator]
来取值,例如
- for...of
- var item
- for (item of[100, 200, 300]) {
- console.log(item)
- }
- // 打印出:100 200 300
- // 注意,这里每次获取的 item 是数组的 value,而不是 index ,这一点和 传统 for 循环以及 for...in 完全不一样
而具有
属性的对象,都可以一键生成一个
- [Symbol.iterator]
对象。如何生成以及生成之后什么样子,还有生成之后的作用,下文分解。
- Iterator
不要着急,也不要跳过本文的任何步骤,一步一步跟着我的节奏来看。
对象
- Iterator
定义一个数组,然后生成数组的
对象
- Iterator
- const arr = [100, 200, 300]
- const iterator = arr[Symbol.iterator]() // 通过执行 [Symbol.iterator] 的属性值(函数)来返回一个 iterator 对象
好,现在生成了
,那么该如何使用它呢 ———— 有两种方式:
- iterator
和
- next
。
- for...of
先说第一种,
- next
- console.log(iterator.next()) // { value: 100, done: false }
- console.log(iterator.next()) // { value: 200, done: false }
- console.log(iterator.next()) // { value: 300, done: false }
- console.log(iterator.next()) // { value: undefined, done: true }
看到这里,再结合上一节内容,是不是似曾相识的感觉?(额,没有的话,那你就回去重新看上一节的内容吧)
对象可以通过
- iterator
方法逐步获取每个元素的值,以
- next()
形式返回,
- { value: ..., done: ... }
就是值,
- value
表示是否到已经获取完成。
- done
再说第二种,
- for...of
- let i
- for (i of iterator) {
- console.log(i)
- }
- // 打印:100 200 300
上面使用
遍历
- for...of
对象,可以直接将其值获取出来。这里的 "值" 就对应着上面
- iterator
返回的结果的
- next()
属性
- value
返回的也是
- Generator
对象
- Iterator
看到这里,你大体也应该明白了,上一节演示的
,就是生成一个
- Generator
对象。因此才会有
- Iterator
,也可以通过
- next()
来遍历。拿出上一节的例子再做一次演示:
- for...of
- function* Hello() {
- yield 100
- yield (function () {return 200})()
- return 300
- }
- const h = Hello()
- console.log(h[Symbol.iterator]) // [Function: [Symbol.iterator]]
执行
得到的就是一个
- const h = Hello()
对象,因为
- iterator
是有值的。既然是
- h[Symbol.iterator]
对象,那么就可以使用
- iterator
和
- next()
进行操作
- for...of
- console.log(h.next()) // { value: 100, done: false }
- console.log(h.next()) // { value: 200, done: false }
- console.log(h.next()) // { value: 300, done: false }
- console.log(h.next()) // { value: undefined, done: true }
- let i
- for (i of h) {
- console.log(i)
- }
这一节我们花费很大力气,从
又回归到了
- Iterator
,目的就是为了看看
- Generator
到底是一个什么东西。了解其本质,才能更好的使用它,否则总有一种抓瞎的感觉。
- Generator
接下来我们就
具体有哪些使用场景。
- Generator
前面用两节的内容介绍了
可以让执行处于暂停状态,并且知道了
- Generator
返回的是一个
- Generator
对象,这一节就详细介绍一下
- Iterator
的一些基本用法。
- Generator
本节演示的代码可参考
和
- next
参数传递
- yield
的应用示例
- for...of
语句
- yield*
中的
- Generator
- this
和
- next
参数传递
- yield
我们之前已经知道,
具有返回数据的功能,如下代码。
- yield
后面的数据被返回,存放到返回结果中的
- yield
属性中。这算是一个方向的参数传递。
- value
- function* G() {
- yield 100
- }
- const g = G()
- console.log( g.next() ) // {value: 100, done: false}
还有另外一个方向的参数传递,就是
向
- next
传递,如下代码。
- yield
- function* G() {
- const a = yield 100
- console.log('a', a) // a aaa
- const b = yield 200
- console.log('b', b) // b bbb
- const c = yield 300
- console.log('c', c) // c ccc
- }
- const g = G()
- g.next() // value: 100, done: false
- g.next('aaa') // value: 200, done: false
- g.next('bbb') // value: 300, done: false
- g.next('ccc') // value: undefined, done: true
捋一捋上面代码的执行过程:
时,为传递任何参数,返回的
- g.next()
,这个应该没有疑问
- {value: 100, done: false}
时,传递的参数是
- g.next('aaa')
,这个
- 'aaa'
就会被赋值到
- 'aaa'
内部的
- G
标量中,然后执行
- a
打印出来,最后返回
- console.log('a', a)
- {value: 200, done: false}
有一个要点需要注意,就
是将
- g.next('aaa')
传递给上一个已经执行完了的
- 'aaa'
语句前面的变量,而不是即将执行的
- yield
前面的变量。这句话要能看明白,看不明白就说明刚才的代码你还没看懂,继续看。
- yield
的应用示例
- for...of
针对
在
- for...of
对象的操作之前已经介绍过了,不过这里用一个非常好的例子来展示一下。用简单几行代码实现斐波那契数列。通过之前学过的
- Iterator
知识,应该不能解读这份代码。
- Generator
- function* fibonacci() {
- let [prev, curr] = [0, 1]
- for (;;) {
- [prev, curr] = [curr, prev + curr]
- // 将中间值通过 yield 返回,并且保留函数执行的状态,因此可以非常简单的实现 fibonacci
- yield curr
- }
- }
- for (let n of fibonacci()) {
- if (n > 1000) {
- break
- }
- console.log(n)
- }
语句
- yield*
如果有两个
,想要在第一个中包含第二个,如下需求:
- Generator
- function* G1() {
- yield 'a'
- yield 'b'
- }
- function* G2() {
- yield 'x'
- yield 'y'
- }
针对以上两个
,我的需求是:一次输出
- Generator
,该如何做?有同学看到这里想起了刚刚学到的
- a x y b
可以实现————不错,确实可以实现(大家也可以想想到底该如何实现)
- for..of
但是,这要演示一个更加简洁的方式
表达式
- yield*
- function* G1() {
- yield 'a'
- yield* G2() // 使用 yield* 执行 G2()
- yield 'b'
- }
- function* G2() {
- yield 'x'
- yield 'y'
- }
- for (let item of G1()) {
- console.log(item)
- }
之前学过的
后面会接一个普通的 JS 对象,而
- yield
后面会接一个
- yield*
,而且会把它其中的
- Generator
按照规则来一步一步执行。如果有多个
- yield
串联使用的话(例如
- Generator
源码中),用
- Koa
来操作非常方便。
- yield*
中的
- Generator
- this
对于以下这种写法,大家可能会和构造函数创建对象的写法产生混淆,这里一定要注意 —— Generator 不是函数,更不是构造函数
- function* G() {}
- const g = G()
而以下这种写法,更加不会成功。只有构造函数才会这么用,构造函数返回的是
,而
- this
返回的是一个
- Generator
对象。完全是两码事,千万不要搞混了。
- Iterator
- function* G() {
- this.a = 10
- }
- const g = G()
- console.log(g.a) // 报错
本节基本介绍了
的最常见的用法,但是还是没有和咱们的最终目的————异步操作————沾上关系,而且现在看来有点八竿子打不着的关系。但是话说回来,这几节内容,你也学到了不少知识啊。
- Generator
别急哈,即便是下一节,它们还不会有联系,再下一节就真相大白了。下一节我们又给出一个新概念————
函数
- Thunk
要想让
和异步操作产生联系,就必须过
- Generator
函数这一关。这一关过了之后,立即就可以着手异步操作的事情,因此大家再坚持坚持。至于
- thunk
函数是什么,下文会详细演示。
- thunk
本节演示的代码可参考
函数
- thunk
函数的特点
- thunk
库
- thunkify
就用 nodejs 中读取文件的函数为例,通常都这么写
- fs.readFile('data1.json', 'utf-8', (err, data) => {
- // 获取文件内容
- })
其实这个写法就是将三个参数都传递给
这个方法,其中最后一个参数是一个
- fs.readFile
函数。这种函数叫做 多参数函数,我们接下来做一个改造
- callback
函数
- thunk
改造的代码如下所示。不过是不是感觉越改造越复杂了?不过请相信:你看到的复杂仅仅是表面的,这一点东西变的复杂,是为了让以后更加复杂的东西变得简单。对于个体而言,随性比较简单,遵守规则比较复杂;但是对于整体(包含很多个体)而言,大家都随性就不好控制了,而大家都遵守规则就很容易管理 ———— 就是这个道理!
- const thunk = function (fileName, codeType) {
- // 返回一个只接受 callback 参数的函数
- return function (callback) {
- fs.readFile(fileName, codeType, callback)
- }
- }
- const readFileThunk = thunk('data1.json', 'utf-8')
- readFileThunk((err, data) => {
- // 获取文件内容
- })
先自己看一看以上代码,应该是能看懂的,但是你可能就是看懂了却不知道这么做的意义在哪里。意义先不管,先把它看懂,意义下一节就会看到。
返回的其实是一个函数
- const readFileThunk = thunk('data1.json', 'utf-8')
这个函数,只接受一个参数,而且这个参数是一个
- readFileThunk
函数
- callback
函数的特点
- thunk
就上上面的代码,我们经过对传统的异步操作函数进行封装,得到一个只有一个参数的函数,而且这个参数是一个
函数,那这就是一个
- callback
函数。就像上面代码中
- thunk
一样。
- readFileThunk
库
- thunkify
上面代码的封装,是我们手动来做的,但是没遇到一个情况就需要手动做吗?在这个开源的时代当让不会这样,直接使用第三方的
就好了。
- thunkify
首先要安装
,然后在代码的最上方引用
- npm i thunkify --save
。最后,上面我们手动写的代码,完全可以简化成这几行,非常简单!
- const thunkify = require('thunkify')
- const thunk = thunkify(fs.readFile)
- const readFileThunk = thunk('data1.json', 'utf-8')
- readFileThunk((err, data) => {
- // 获取文件内容
- })
了解了
函数,我们立刻就将
- thunk
和异步操作进行结合
- 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
的框架 ———— Koa
- Generator
是一个 nodejs 开发的 web 框架,所谓 web 框架就是处理 http 请求的。开源的 nodejs 开发的 web 框架最初是 。
我们此前说过,既然是处理 http 请求,是一种网络操作,肯定就会用到异步操作。express 使用的异步操作是传统的
,而 koa 用的是我们刚刚讲的
- callbck
(koa
- Generator
用的是
- v1.x
,已经被广泛使用,而 koa
- Generator
用到了 ES7 中的
- v2.x
,不过因为 ES7 没有正式发布,所以 koa
- async-await
也没有正式发布,不过可以试用)
- v2.x
koa 是由 express 的原班开发人员开发的,比 express 更加简洁易用,因此 koa 是目前最为推荐的 nodejs web 框架。阿里前不久就依赖于 koa 开发了自己的 nodejs web 框架
国内可以通过查阅文档,。
提醒:如果你是初学
而且从来没有用过 koa ,那么这一节你如果看不懂,没有问题。看不懂就不要强求,可以忽略,继续往下看!
- Generator
本节演示的代码可参考
- Generator
- Generator
koa 是一个 web 框架,处理 http 请求,但是这里我们不去管它如何处理 http 请求,而是直接关注它使用
的部分————中间件。
- Genertor
例如,我们现在要用 3 个
输出
- Generator
,我们如下代码这么写。应该能看明白吧?看不明白回炉重造!
- 12345
- let info = ''
- function* g1() {
- info += '1' // 拼接 1
- yield* g2() // 拼接 234
- info += '5' // 拼接 5
- }
- function* g2() {
- info += '2' // 拼接 2
- yield* g3() // 拼接 3
- info += '4' // 拼接 4
- }
- function* g3() {
- info += '3' // 拼接 3
- }
- var g = g1()
- g.next()
- console.log(info) // 12345
但是如果用 koa 的 中间件 的思路来做,就需要如下这么写。
- app.use(function *(next){
- this.body = '1';
- yield next;
- this.body += '5';
- console.log(this.body);
- });
- app.use(function *(next){
- this.body += '2';
- yield next;
- this.body += '4';
- });
- app.use(function *(next){
- this.body += '3';
- });
解释几个关键点
中传入的每一个
- app.use()
就是一个 中间件,中间件按照传入的顺序排列,顺序不能乱
- Generator
表示下一个中间件。
- next
就是先将程序暂停,先去执行下一个中间件,等
- yield next
被执行完之后,再回过头来执行当前代码的下一行。因此,koa 的中间件执行顺序是一种,不过这里看不懂也没问题。
- next
可以共享变量。即第一个中间件改变了
- this
的属性,在第二个中间件中可以看到效果。
- this
前方高能————上面介绍 koa 的中间价估计有些新人就开始蒙圈了,不过接下来还有更加有挑战难度的,就是以上这种方式是如何实现的。你就尽量去看,看懂了更好,看不懂也没关系————当然,你完全可以选择跳过本教程直接去看下一篇,这都 OK
加入我们自己实现一个简单的 koa ———— MyKoa ,那么仅需要几十行代码就可以搞定上面的问题。直接写代码,注意看重点部分的注释
- class MyKoa extends Object {
- constructor(props) {
- super(props);
- // 存储所有的中间件
- this.middlewares = []
- }
- // 注入中间件
- use (generator) {
- this.middlewares.push(generator)
- }
- // 执行中间件
- listen () {
- this._run()
- }
- _run () {
- const ctx = this
- const middlewares = ctx.middlewares
- co(function* () {
- let prev = null
- let i = middlewares.length
- //从最后一个中间件到第一个中间件的顺序开始遍历
- while (i--) {
- // ctx 作为函数执行时的 this 才能保证多个中间件中数据的共享
- //prev 将前面一个中间件传递给当前中间件,才使得中间件里面的 next 指向下一个中间件
- prev = middlewares[i].call(ctx, prev);
- }
- //执行第一个中间件
- yield prev;
- })
- }
- }
最后我们执行代码实验一下效果
- var app = new MyKoa();
- app.use(function *(next){
- this.body = '1';
- yield next;
- this.body += '5';
- console.log(this.body); // 12345
- });
- app.use(function *(next){
- this.body += '2';
- yield next;
- this.body += '4';
- });
- app.use(function *(next){
- this.body += '3';
- });
- app.listen();
的应用基本讲完,从一开始的基础到后面应用到异步操作,再到本节的高级应用 koa ,算是比较全面了。接下来,我们要再回到最初的起点,探讨
- Generator
的本质,以及它和
- Generator
的关系。
- callback
还是那句话,搞明白原理,才能用的更加出色!
其实标题中的问题,是一个伪命题,因为
和
- Generator
根本没有任何关系,只是我们通过一些方式(而且是很复杂的方式)强行将他俩产生了关系,才会有现在的
- callback
处理异步。
- Generator
的本质
- Generator
的结合
- callback
的本质
- Generator
介绍
的中,多次提到 暂停 这个词 ———— "暂停" 才是
- Generator
的本质 ———— 只有
- Generator
能让一段程序执行到指定的位置先暂停,然后再启动,再暂停,再启动。
- Generator
而这个 暂停 就很容易让它和异步操作产生联系,因为我们在处理异步操作时,即需要一种 "开始读取文件,然后暂停一下,等着文件读取完了,再干嘛干嘛..." 这样的需求。因此将
和异步操作联系在一起,并且产生一些比较简明的解决方案,这是顺其自然的事儿,大家要想明白这个道理。
- Generator
不过,JS 还是 JS,单线程还是单线程,异步还是异步,
还是
- callback
。这一切都不会因为有一个
- callback
而有任何变化。
- Generator
的结合
- callback
之前在介绍
的最后,拿
- Promise
和
- Promise
做过一些比较,最后发现
- callback
其实是利用了
- Promise
才能实现的。而这里,
- callback
也必须利用
- Generator
才能实现。
- callback
拿介绍
时的代码举例(代码如下),如果
- co
后面用的是
- yield
函数,那么
- thunk
函数需要的就是一个
- thunk
参数。如果
- callback
后面用的是
- yield
对象,
- Promise
和
- Promise
的联系之前已经介绍过了。
- callback
- co(function* () {
- const r1 = yield readFilePromise('some1.json')
- console.log(r1) // 打印第 1 个文件内容
- const r2 = yield readFileThunk('some2.json')
- console.log(r2) // 打印第 2 个文件内容
- })
因此,
离不开
- Generator
,
- callback
离不开
- Promise
,异步也离不开
- callback
。
- callback
如果你看完了,感觉还不错,欢迎给我打赏 ———— 以激励我更多输出优质内容
最后,github 地址是 欢迎 star 和 pr
来源: http://www.cnblogs.com/wangfupeng1988/p/6532713.html