# 异步编程

  • 回调函数
  • 事件监听
  • 发布/订阅
  • Promise 对象
  • Generator 函数
  • async 函数

# 回调函数

  • 回调函数的优点是简单、容易理解和实现,
  • 缺点是不利于代码的阅读和维护,各个部分之间高度耦合(coupling),使得程序结构混乱、流程难以追踪(尤其是多个回调函数嵌套的情况),而且每个任务只能指定一个回调函数

# 事件监听

  • 采用事件驱动模式。异步任务的执行不取决于代码的顺序,而取决于某个事件是否发生
  • 优点是比较容易理解,可以绑定多个事件,每个事件可以指定多个回调函数,而且可以“去耦合”(decoupling),有利于实现模块化。
  • 缺点是整个程序都要变成事件驱动型,运行流程会变得很不清晰。阅读代码的时候,很难看出主流程

# 发布/订阅(观察者模式)

  • 这种方法的性质与“事件监听”类似,但是明显优于后者。因为可以通过查看“消息中心”,了解存在多少信号、每个信号有多少订阅者,从而监控程序的运行

# 异步操作的流程控制

  • 如果有多个异步操作,就存在一个流程控制的问题:如何确定异步操作执行的顺序,以及如何保证遵守这种顺序
  • 串行执行
  • 并行执行
  • 并行与串行的结合

# Promise 对象

  • Promise 对象就是为了解决这个问题而提出的。它不是新的语法功能,而是一种新的写法,允许将回调函数的嵌套,改成链式调用
  • Promise 的写法只是回调函数的改进,使用then方法以后,异步任务的两段执行看得更清楚了,除此以外,并无新意。
  • Promise 的最大问题是 代码冗余,原来的任务被 Promise 包装了一下,不管什么操作,一眼看去都是一堆then,原来的语义变得很不清楚

# Generator 函数

  • Generator 函数的最大特点:可以暂停执行和恢复执行
  • 它可以用看似完全同步的代码(除了yield关键字本身),去书写异步流程

# Generator 函数可以作为异步编程的完整解决方案原因:

  • 可以暂停执行和恢复执行
  • 函数体内外的数据交换
    • next返回值的 value 属性,是 Generator 函数向外输出数据;next方法还可以接受参数,向 Generator 函数体内输入数据
  • 错误处理机制
    • Generator 函数内部还可以部署错误处理代码,捕获函数体外抛出的错误

# async 函数

# async 函数是什么?

  • async function本质上就是生成器+promise+run(..)模式的语法糖;它们底层的运作方式是一样的!
  • 生成器yield出Promise,然后其控制生成器的迭代器来执行它
  • 组合Promise和看似同步的流程控制代码

# async原理

const fs = require('fs')

function readFile(filename){
  return new Promise((resolve, reject)=>{
    fs.readFile('./store.js', 'utf-8', (err, data)=>{
      if(!err){
        return resolve(data)
      }
    })
  })
}

// async function fn(...args){
//   const data = await readFile('./store.js')
//   console.log(data)
// }
fn()
function fn(...args){
  function spawn(genF){
    return new Promise((resolve, reject)=>{
      const gen = genF()
      function step(nextF){
        let next
        try{
          next = nextF()
        }catch(e){
          return reject(e)
        }
        if(next.done){
          return resolve(next.value)
        }
        Promise.resolve(next.value).then(v=>{
         step(()=>gen.next(v))
        }).catch(e=>{
          step(()=>gen.throw(e))
        })
      }
      step(()=>gen.next(undefined))
    })
  }
  return spawn(function *(){
    const data = yield readFile('./store.js')
    console.log(data)
  })
}
  • 异步流程控制,执行一组promise函数,执行方式,串行,并行,控制最大并发数执行,使用原生方法,promise方法,即async/await实现
const fs = require('fs')

function promisify(fn, ctx){
  return (...args) =>{
    return new Promise((resolve, reject)=>{
      fn.apply(ctx, [...args, 'utf-8', (err, data)=>{
        if(err) return reject(err)
        return resolve(data)
      }])
    })
  }
}
const readFile = promisify(fs.readFile)

async function fn(filename){
  const data = await readFile(filename)
  console.log(data)
}
// fn('./proxy.js')

let items = [1,2,3,4,5]
let results = []

function series(item){
  if(item){
    setTimeout(()=>{
      console.log('arg: ', item)
      results.push(item*2)
      series(items.shift())
    }, 2000)
  }else{
    console.log('results:', results)
  }
}

function logInOrder(urls){
  //远程读取所有的urls
  const textPromises = urls.map(url=>{
    return fetch(url).then(response=>response.text())
  })
  //按次序输出
  textPromises.reduce((chain, textPromise)=>{
    return chain.then(()=>textPromise)
      .then(text=>console.log(text))
  }, Promise.resolve())
}

async function logInOrder(urls){
  for(const url of urls){
    const response = await fetch(url)
    console.log(await response.text())
  }
}
async function logInOrder(urls){
  //并发读取远程url
  const textPromises = urls.map(async url=>{
    const response = await fetch(url)
    return response.text()
  })
  //按次序输出
  for(const textPromise of textPromises){
    console.log(await textPromise)
  }
}


// series(items.shift())

// for(let i=0,len=items.length; i<len; i++){
//   setTimeout(()=>{
//     console.log('arg: ', items[i])
//     results.push(items[i]*2)
//     if(results.length === len){
//       console.log('results:', results)
//     }
//   },2000) 
// }

let running = 0
let limit = 2

function launcher(){
  while(running<limit && items.length>0){
    let item = items.shift()
    setTimeout(()=>{
      results.push(item*2)
      running--
      launcher()
      if(running === 0){
        console.log('results: ', results)
      }
    }, 2000)
    running++
    console.log('arg: ', item)
  }
}
// launcher()

let tasks = [1,2,3,4,5,6,7,8,9,10]
let limit = 3
const readFile = function(file){
  return new Promise((resolve, reject)=>{
    setTimeout(()=>{
      console.log('success', file)
      resolve(file)
    }, 5000)
  })
}

async function sendRequest(tasks, limit){
  return new Promise((resolve, reject)=>{
    let counter = 0
    let len = tasks.length
    let start = async ()=>{
      let task = tasks.shift()
      console.log('start: ',task)
      if(task){
        await readFile(task)
        if(counter===len-1){
          console.log('全部完成')
          resolve()
        }else{
          counter++
          start()
        }
      }
    }
    while(limit>0){
      setTimeout(()=>{
       start()
      }, Math.random()*7000)
      limit--
    }
  })
}

sendRequest(tasks,limit)