zzzmj/duola-blog

javascript异步编程第二步:co库

zzzmj opened this issue · 0 comments

zzzmj commented

前言

为了解决第一步中的两个问题

  1. 需要显式的以gen.next()来启动
  2. 转交下一步流程控制需要写到回调中,而且遇到不同的异步代码时,难以通用

其实也就是我们得想办法让generator函数能够自动执行

于是乎,TJ大神出了一个CO库,来解决这两个问题

早期的CO库基于Thunk化实现,现在的CO库是基于Promise的

Thunk化

Thunk化是什么呢? 例如一个readFile函数

// 普通的readFile
readFile('file.txt', (err, data) => {
  console.log(data.toString())
})

// thunk化后的readFile
var readFile = function(filename) => {
  return function(callback) {
    fs.readFile(filename, callback)
  }
}

var thunk = readFile('file.txt')
thunk((err,data) => {
  console.log(data.toString())
})

其实thunk化有点类似柯里化的概念,将多个入参,转变成一个入参的新函数,并且这个新函数只接收回调函数作为参数

我们可以将thunk化封装一下做一个thunkify,将函数自动包装为一个thunk化的函数

// 这个thunkify并不能用于生产环境,特殊情况没有考虑
var thunkify = function(fn) {
  return function() {
    var args = [].slice.call(arguments)
    return function(callback) {
      args.push(callback)
      fn.apply(this, args)
    }
  }
}

var readFile = thunkify(fs.readFile)
readFile('file.txt')((err,data) => {
  console.log(data.toString())
})

然后使用这种方式我们来改写之前异步的代码

var readFile = thunkify(fs.readFile)
var read = function* (arr) {
  var file1 = yield readFile('1.txt')
  console.log('file1', file1.toString())
  var file2 = yield readFile('2.txt')
  console.log('file2', file2.toString())
  var file3 = yield readFile('3.txt')
  console.log('file3', file3.toString())
}

var gen = read()

var t = gen.next()
t.value((err, data) => {
  t = gen.next(data)
  t.value((err, data) => {
    t = gen.next(data)
    t.value((err, data) => {
      t = gen.next(data)
    })
  })
})
// 这里注意next的调用
// 因为next方法的参数表示上一个yield表达式的返回值,第一次next的参数无效,用以启动生成器

由于我们将readFile thunk化了,所以每次yield后,返回的都是readFile处理完之后的回调函数

这个时候,我们可以发现gen.next()这个过程虽然嵌套了很多回调,但其实都是相同的代码,可以自动化掉

编写一个run函数来自动化这个过程

var run = function(fn) {
  var gen = fn()
  function next(err, data) {
    var res = gen.next(data)
    if (res.done) {
      return 
    }
    res.value(next)
  }
  next()
}

所以我们的代码由异步代码转变成了'同步代码'

var read = function* (arr) {
  var file1 = yield readFile('1.txt')
  console.log('file1', file1.toString())
  var file2 = yield readFile('2.txt')
  console.log('file2', file2.toString())
  var file3 = yield readFile('3.txt')
  console.log('file3', file3.toString())
}

run(read)

Thunk 函数并不是 Generator 函数自动执行的唯一方案。因为自动执行的关键是,必须有一种机制,自动控制 Generator 函数的流程,接收和交还程序的执行权。回调函数可以做到这一点,Promise 对象也可以做到这一点。

Promise

其实Generator 函数就是一个异步操作的容器。它的自动执行需要一种机制,当异步操作有了结果,能够自动交回执行权。

之前使用Thunk化的方法实现了这个自动执行的机制

现在基于Promise来实现

将readFile函数改造成Promise形式,手动来执行

var readFile = function(filename) {
  return new Promise((resolve, reject) => {
    fs.readFile(filename, (err, data) => {
      if (err) {
        reject(err)
      }
      resolve(data)
    })
  })
}

var read = function* () {
  var file1 = yield readFile('1.txt')
  console.log('file1', file1.toString())
  var file2 = yield readFile('2.txt')
  console.log('file2', file2.toString())
  var file3 = yield readFile('3.txt')
  console.log('file3', file3.toString())
}

var gen = read()

gen.next().value.then(data => {
  gen.next(data).value.then(data => {
    gen.next(data).value.then(data => {
      gen.next(data)
    })
  })
})

自动化掉手动执行的过程

var run = function(fn) {
  var gen = fn()
  function next(data) {
    var res = gen.next(data)
    if (res.done) return 
    res.value.then(data =>{
      next(data)
    })
  }
  next()
}

run(read)