异步发展历程
为什么会有异步?
首先我们要简单的了解一下同步和异步的概念
同步:调用一旦开始,调用者必须等到调用方法返回后,才能继续后续的行为。调用者会主动等待调用的结果。
异步:当一个异步调用发出后,这个调用就立刻返回了,调用者不会立即得到结果。而是通过某些通知来通知调用者,或者通过回调函数来处理这个调用。
推荐一篇关于同步和异步的文章,感兴趣的同学可以了解一下。
我们来想象一下这样的场景,在你的业务中,需要从服务端去获取http请求信息。这个时候你利用ajax向服务器发了请求。
$.get(url,
function
(data) {console.log(data)
});
假如这个请求是同步执行的,那么,也就是说你需要等待ajax的响应。但是在网络请求中,请求的速度会有很大的波动,也许对方的服务器会挂掉,这样会导致一直无法得到响应,那么我们后续事情也无法继续。所以为了提高工作效率,我们想要的是,当我们发出请求时,我们只关心响应后对数据的处理,并在等待这段响应的过程中,去执行其他操作。所以,我们需要异步,来提高效率
javascript异步发展经历
那么,我们知道了,在大多数获取资源的操作中,我们需要异步操作。那么javascript异步是怎么一步一步发展的呢?
1.回调函数
我们可以通过回调函数来进行异步操作。
例子:异步读取一个文件
let fs = require('fs')
fs.readFile('./1.txt','utf8',function(err,data){
if(err){//如果err有值,就表示程序出错了
console.log(err);
}else{/
/如果error为空,就表示 成功了,没有错误
console.log(data);
}
})复制代码
回调函数有个特点:error first --> 调用回调函数的时候第一个参数永远是错误对象
有了回调函数似乎问题得到了解决,但是在使用的过程中,我们难免会发现回调函数也有他的不足之处:
- 无法错误捕捉 try catch 原因是: 尝试对异步方法进行try/catch操作只能捕获当次事件循环内的异常,对call back执行时抛出的异常将无能为力。也就是说,当你准备捕获异常时,异步错误发生在try catch块结束的时候。
- 异步操作不能return ,实例如下(返回值为undefined):
// 发送ajax请求
$.ajax({
type: "POST",
url: "checkName.php",
data: {
username: $inputVal
},
success: function(responseText) {
if (responseText === "helloworld") {
return false;
} else {
return true;
}
}
});
// 原因:异步操作,可能无法取到数据就直接返回,所以只等返回undefined。
复制代码
3. 如果异步操作过多,会造成回调地狱。
fs.readFile('./template.txt', 'utf8', function (err, template) {
fs.readFile('./data.txt', 'utf8', function (err, data) {
console.log(template + ' ' + data);
})
})复制代码
2.解决回调嵌套问题
1.通过事件发布订阅来
先上代码:
let EventEmitter = require('events');let eventByDep = new EventEmitter();
let html = {};
//监听数据获取成功事件,当事件发生之后调用回调函数
eventByDep.on('ready',function(key,value){
html[key] = value;
if(Object.keys(html).length == 3){
//打印出template的值
console.log(html);
}
});
fs.readFile('./data1.txt', 'utf8', function (err, data1) {
//1 事件名 2 参数往后是传递给回调函数的参数
eventByDep.emit('ready','data1',data1);
})
fs.readFile('./data2.txt', 'utf8', function (err, data2) {
eventByDep.emit('ready','data2',data2);
})复制代码
2.通过设置一个哨兵函数来处理
优点:不需要额外引入包
原理:通过调用哨兵函数来监控文件的异步操作,代码如下:
//哨兵函数,用来监听文件异步操作的返回值.
function done(key,value){ html[key] = value;
if(Object.keys(html).length == 2){
console.log(html);
}
}
fs.readFile('./data1.txt', 'utf8', function (err, data1) {
done('data1',data1);
})
fs.readFile('./data2.txt', 'utf8', function (err, data2) {
done('data2',data2);
})
//也可以写一个高级函数用来生成哨兵函数:
function render(length,cb){ let html={}; return function(key,value){
html[key] = value;
if(Object.keys(html).length == length){
cb(html);
}
}
}
let done = render(3,function(html){
console.log(html);
});
fs.readFile('./data1.txt', 'utf8', function (err, data1) {
done('data1',data1);
})fs.readFile('./data2.txt', 'utf8', function (err, data2) {
done('data2',data2);
})
fs.readFile('./data3.txt', 'utf8', function (err, data3) {
done('data3',data3);
})复制代码
3.promise的到来
以上的异步处理,都有或多或少的缺点,那么promise的到来,让js的异步处理变得方便,优雅。那么promise是如何实现的呢?
3.1 手动实现一个简单的promise
1. 首先我们创建了三个状态常量分别表示,挂起,失败,成功三种状态。
2. 一开始 Promise 的状态应该是 pending
3.value变量用于保存 resolve 或者 reject 中传入的值
4.resolvedCallbacks 和 rejectedCallbacks 用于保存 then中的回调,因为当执行完 Promise 时状态可能还是等待中,这时候应该把 then中的回调保存起来用于状态改变时使用.
5.规范规定只有等待状态(PENDING)才能改变成其他状态。
6.then方法中有两个函数,分别为失败和成功执行的回调。
// 创建三种状态
const PENDING = 'pending'
const RESOLVED = 'resolved'
const REJECTED = 'rejected'
function Promise(executor){
const self = this
self.state = PENDING //默认为pending态 self.value = null
self.resaon = reason //失败的原因
self.resolvedCallbacks = [] self.rejectedCallbacks = []
// 成功态
function resolve(value){
if (self.state === PENDING){
self.state = RESOLVED
self.value = value
//执行回调函数
self.resolvedCallbacks.forEach(fn=>fn())
}
}
//失败态
function reject(reason) {
if (self.status === 'pending') {
self.status = 'rejected';
self.reason = reason;
self.rejectedCallbacks.forEach(fn=>fn())
}
}
try {
executor(resolve, reject)}catche(e){
// 捕获的时候发生异常,就直接失败了
reject(e);
}}
//实现promise.then方法
Promise.prototype.then = function(onFulfilled, onRejected) {
const self = this
//判断onFulfilled,onRejected是否是函数类型,不是则构造函数,并赋值给对应的参数,并实现透传。
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
onRejected =
typeof onRejected === 'function'
? onRejected
: r => {
throw r
}
if (self.state === PENDING) {
self.resolvedCallbacks.push(onFulfilled)
self.rejectedCallbacks.push(onRejected) }
if (self.state === RESOLVED) {
onFulfilled(self.value)
}
if (that.state === REJECTED) {
onRejected(self.value) }
}
//使用
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1)
}, 0)
}).then(value => {
console.log(value)
})复制代码
以上只是一个简易版本的promise。
那么promise边解决了异步回调的回调地狱等问题.
4.利用Generator + promise来实现异步
首先我们来认识一下Generator
- 我们用*来标识一个Generator函数。
- 每次执行都会在yield停止。调用一次next就会继续向下执行。
- 返回结果是一个迭代器 迭代器有一个next方法
- yield后面跟着的是value的值
- yield等号前面的是我们当前调用next传进来的值
- 第一次next传值是无效的.
代码实例如下:
function* read() {
console.log(1);
let a = yield 'hello';
console.log(a);
let b = yield 9
console.log(b);
return b;
}
let it = read();
console.log(it.next('')); // {value:'hello',done:false}
console.log(it.next('100')); // {value:9,done:false}
console.log(it.next('200')); // {value:200,done:true
}console.log(it.next('200')); // {value:undefined,done:true}复制代码
一般利用Generator + Promise来实现异步:
let bluebird = require('bluebird');let fs = require('fs');
//将文件的读取操作Promise化
let read = bluebird.promisify(fs.readFile);
function* r() {
let content1 = yield read('./1.txt', 'utf8');
let content2 = yield read(content1, 'utf8');
return content2;
}
//利用next来执行异步
let g = r();
g.next().value.then(function (data) {
g.next(data).value.then(function (data) {
g.next(data)
})
})
复制代码
接下来我们使用co库(自己实现) 可以自动的将generator进行迭代
function(it){
return new Promise(function(resolve,reject){
function next(data){
let {value,done} = it.next(data);
if (!done){
value.then(function(data){
//将值往下传递
next(data)
},reject)
}else{
resolve(value)
}
}
next();
})
}
//使用
co(r()).then(function (data) {
console.log(data)
})
复制代码
5.async + await
语法如下:
// 只能在async函数内部使用
let value = await promise复制代码
关键词await
可以让JavaScript进行等待,直到一个promise执行并返回它的结果,JavaScript才会继续往下执行。
也就是说async + await 相当于 co + generator 他实现了一个语法糖.
实例如下:
let bluebird = require('bluebird');
let fs = require('fs');
let read = bluebird.promisify(fs.readFile);
async function r(){
try{
let content1 = await read('./text','utf8');
let content2 = await read(content1,'utf8');
return content2;
}catch(e){
// 如果出错会catch
onsole.log('err',e)
}
}
// async函数返回的是promise,
r().then(function(data){
console.log('flag',data);
},
function(err){
console.log(err);
})复制代码
那么async+await有哪些好处呢?
- 解决了回调地狱
-
并发执行异步,在同一时刻同步返回结果。
-
解决了返回值的问题
-
可以实现代码的try/catch
以上便是我总结的javascript的发展历程,不足之处,还请各位大佬多多指正。谢谢!