js异步编程简述

223 阅读3分钟

要解决的问题

任务: 在取到文件名为finename1的文件之后, 在取filename2, 然后在取filename3

传统的回调嵌套:

fs.readFile(filename1, function (err, data) {
    if(!err){
        rs.readFile(filename2, function (err, data) {
            if(!err){
                rs.readFile(filename3, function (err, data)...
            }else{
            }
        }
    }else{
        ...
    }
})

目标: 将异步代码以同步的形式表达

data1 = 异步请求1
// 用data1做一些事情
data2 = 异步请求2
// 用data2做一些事情
data3 = 异步请求3
// 用data3做一些事情

方案就是: Promise + Generator = Async

Promise

Promise是异步回调嵌套的解决方案之一, Promise能看作一个异步流程机, 数个Promise组成一个完成流程, Promise提供相应的接口实现对接

es6的Promise实现遵循Promise A+ 规范 Promise A+ Promise A+规范的翻译

Promise的异步编程能力

利用Promise封装fs.readFile

Promise的表达

// 封装fs.readFile方法
const fs = require('fs')
const readFile = filename => new Promise((resolve, reject) => {
    fs.readFile(filename, (err, data) => {
        if (err) {
            reject(err)
        } else {
            resolve(data.toString())
        }
    })
})

// 利用封装的Promise去解决任务
readFile(filename1)
  .then(v => {
    return readFile(filename2)
  })
  .then(v => {
    return readFile(filename3)
  })
  ...

缺点

Promise封装异步操作有几个缺点

  1. 可读性比传统的好, 不够完美, 会有Promise自身的沉冗代码
  2. Promise一旦创立立即执行函数, 可能需要外面在封装一层函数实现延迟执行, 不够灵活

generate函数

Generator 函数是 ES6, 提供的一种异步编程解决方案,语法行为和运行机制与传统函数完全不同。

  • 传统函数 只能调用并且传入参数
function foo () { // 创建
  //...dosomething
}

foo(arg) // 调用函数并且传入参数

创建函数 --> 调用函数

  • generate函数 generate函数在函数体外有完全的流程控制权, 每一个yield关键字都相当于一个流程的断点
const fn1 = function () {//...}
const fn2 = function () {//...}
function* foo () { // 创建函数
  const data1 = yield fn1()
  const data2 = yield fn2()
}

const f = foo() // 创建一个迭代器, 函数里面代码不会运行
f.next() // 执行fn1()
f.next(arg) // 执行fn2()并且将值传入函数内, data1接收这个值
f.throw(new Error()) // 将错误传入函数内
f.return() // 将函数return掉

结合Promise

下面是generate结合Promise的异步流程封装, 先写一个手动执行器

function* read() {
  const a = yield readFile('a.txt')
  console.log(a)
  const b = yield readFile('b.txt')
  console.log(b)
}
const spwan = function (gen) {
    const g = gen()
    let step1 = g.next()
    step1.value.then(v => {
      let step2 = g.next(v)
      step2.value.then(v => {
        let step3 = g.next(v)
        //...
      })
    })
}
spwan(read)

可以看出上面是一个递归

generate异步自动执行器

结合的具体思路其实就是在yield后面的Promise在追加一个回调函数, 这个回调函数调用generator g.next(v), 这样就能利用递归跑完所有的yield

function* read() {
  const a = yield readFile('a.txt')
  console.log(a)
  const b = yield readFile('b.txt')
  console.log(b)
}

const spwan = function (gen) {
    const g = gen()
    function next(data) {
      const {value, done} = g.next(data)
      if(done){ // 归
        return
      }
      value.then(data => {// 递
        next(data)
      })
    }
    next()
}

spwan(read)

上述的自动执行器已经可以将read函数里面的异步流程全部执行了

异步流程已经以同步形式执行了出来, 可读行非常高

async

async是ES7的特性,是上述方法的语法糖

async function read() {
  const a = await readFile('a.txt')
  const b = await readFile('b.txt')
  return a + b
}

read()
  .then(v => {
    console.log(v)
  })

async在js引擎层面实现了一个执行器

上述代码read()相当于spwan(read)

await 相当于yield

总结

fs.readFile(filename1, function (err, data) {
    if(!err){
        rs.readFile(filename2, function (err, data) {
            if(!err){
                rs.readFile(filename3, function (err, data)...
            }else{
            }
        }
    }else{
        ...
    }
})

↓ ↓ ↓

async function read() {
  const a = await readFile('a.txt')
  const b = await readFile('b.txt')
  return a + b
}

支持

node7.6已经是默认支持async, ie想都别想 (手动滑稽, 绝大部分标准浏览器已经实现

水平有限, 欢迎指正哈