JavaScript 异步编程的几个方法
原文地址
基于浏览器事件轮回机制(以及nodejs中的事件轮询机制),JavaScript常常会运行在异步环境中。而JavaScript是单线程语言,使用对js的异步操作是不可避免的。
解决方法有以下几点:
1,回调
回调是比较原始的方法
ep:
// fs => node的文件系统
const fs = require('fs');
const readFileAsArray = function(file,cb){
fs.readFile(file,(err,data)=>{
if(err) return cb(err);
// \n => 回车键
const lines = data.toString().trim().split('\n');
cb(null,lines);
})
}
readFileAsArray('./numbers.txt',(err,lines)=>{
if(err) throw err;
const numbers = lines.map(Number);
console.log(`分别抢到了${numbers}块红包`)
})
// 分别抢到了1,2,3,4,5,6,7,9,10,11,12,13块红包
上面的例子,通过readFileAsArray()函数的结果,回调readFileAsArray()函数。
这个例子充分说明了,当代码逻辑复杂或操作步骤多的时候,会写很多层,很难阅读和维护(即使是自己写的,也很难快速上手)。
2,Promise
像上面的回调,他的执行顺序是控制在代码手里(你没办法去手动调整执行顺序---必须干完才干一件事,没有一个显示化的流程),而promise让控制权回到的人的手里,流程更干净,可视化(通过一系列方法---then/catch/all/race等控制异步流程)。
const fs = require('fs');
const readFileAsArray = function(file){
return new Promise((resolve,reject)=>{
fs.readFile(file,(err,data)=>{
if(err){
reject(err);
}
const lines = data.toString().split('\n');
resolve(lines);
})
})
}
readFileAsArray('./numbers.txt').then(
lines=>{
const numbers = lines.map(Number);
console.log(`${numbers}`)
}
).catch(error=>{
console.log(error)
})
但Promise也有单值/不可取等缺点
3,await/async
await/async是ES7推出的语法糖,内部封装了Promise和Generator的组合使用方式。(程序员懒--PromiseAPI太多---await/async就出现了)
const fs = require('fs');
const readFileAsArray = function(file){
return new Promise((resolve,reject)=>{
fs.readFile(file,(err,data)=>{
if(err){
reject(err);
}
const lines = data.toString().split('\n');
resolve(lines);
})
})
}
async function result(){
try{
const lines = await readFileAsArray('./numbers.txt');
const numbers = lines.map(Number);
console.log(`${numbers}`)
} catch (err){
console.log("await 出错!");
console.log(err);
}
}
result();
4,event
回调(promise、await/async)和event的关系就像计划经济和市场经济,一个人为控制,一个根据需求和供给控制。
// 当``EventEmitter``对象触发一个事件时,所绑定在该事件上的函数都被同步调用,监听器的返回值会被丢弃。
// eventEmitter.on => 用于注册监听器
// eventEmitter.emit() =>用于触发改事件
const EventEmitter = require('events');
const fs = require('fs');
class MyEventEmitter extends EventEmitter {
executeAsy(asyncFunc,args){
this.emit('begin');
console.time('执行耗时');
asyncFunc(args,(err,data)=>{
if(err) return this.emit('err',err);
this.emit('data',data);
console.timeEnd('执行耗时');
this.emit('结束')
})
}
}
const myEventEmitter = new MyEventEmitter();
myEventEmitter.on('开始',()=>{
console.log('开始执行了');
})
myEventEmitter.on('data',data=>{
console.log(`${data}`)
})
myEventEmitter.on('结束',()=>{
console.log('结束执行了')
})
myEventEmitter.on('err',err=>{
console.log(err)
})
myEventEmitter.executeAsy(fs.readFile,'./numbers.txt')
event => 代码量太多
5,rxjs
rxjs和异步的关系:它可以把数据转化成一股流,无论是数据同步得到的还是异步得到的,是单值还是多值。
Rx.Observable.of用来包装单值同步数据,
Rx.Observable.fromPromise 用来包装单值异步数据
Rx.Observable.fromEvent用来包装多值异步数据
// import { Observable } from 'rxjs'
const fs = require('fs');
const Rx = require('rxjs');
const Observable = require('rxjs')
const EventEmitter = require('events');
class MyEventEmitter extends EventEmitter{
async executeAsy(asyncFunc,args){
this.emit("开始");
try{
console.time('执行耗时');
const data = await asyncFunc(args);
this.emit('data',data);
console.timeEnd('执行耗时');
this.emit('结束');
} catch(err){
console.log('出错了!')
this.emit('error',err)
}
}
}
const readFileAsArray = function(file){
return new Promise((resolve,reject)=>{
fs.readFile(file,(err,data)=>{
if(err){
reject(err);
}
const lines = data.toString().split('\r\n');
resolve(lines);
})
})
}
const myEventEmitter = new MyEventEmitter();
myEventEmitter.executeAsy(readFileAsArray,'./numbers.txt');
let dataObservable = Observable.fromEvent(myEventEmitter,'data');
let subscription = dataObservable.subscribe(data=>{
console.log(`${data}`)
},err=>{
console.error(err);
},compelete=>{
console.info('compelete!');
})
rxjs还有很多重要的概念,比如生产者Observe和消费者Observable、推拉模型、各种方便的操作符和函数式编程等。
ES8已经着手Observable和Observe的实现,node也在着手异步生命周期钩子Async Hooks来方便程序员调试异步程序,未来的js异步编程会越来越容易,功能也会越来越强大。