# 异步编程
- 回调函数
- 事件监听
- 发布/订阅
- 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)