【浅谈】JavaScript异步操作和Promise
JavaScript异步操作和Promise
博主最近由于需要在学习社区进行一次技术分享,所以选择的题目就是JavaScript异步操作和promise,现在将资料整理为博客
1、JavaScript特性
首先JavaScript是单线程的吧,所以它本身并不可能是异步的。但是JS的承载环建(如浏览器、Node)是多线程的,这让JS具备了一定的异步属性
2、JavaScript异步API
在JS的异步操作中,我们应该熟识有哪些是异步操作的API,如:
- setTimeout
- readFile(Node环建下)
- writeFile(Node环建下)
- 最常用的Ajax
- 。。。。
3、异步操作分析
现在,有一段代码是这样的,我们可以一起来分析分析如下代码
function add(x,y){
console.log(1)
setTimeout(function(){
console.log(2)
var ret = x + y
return ret
},1000)
console.log(3)
}
console.log(add(1,2))
这段代码很简单,程序首先会进入add方法,然后输出1,之后遇到异步操作setTimeout时并不会等着setTimeout执行完,而是接着输出3,到此,函数体由于没有返回值,则会再输出一个undefined ,然后经过1S后,程序输出2
如下:
可以看到,我们并不能拿到异步操作中的结果 ret ,所以现在我们有一个需求就是拿到异步操作中的结果,
这里,我写了两个大家可能会想到的方法
- 第一种方法
但是这种方法是行不通的,程序的输出依旧是undefined - 第二种方法
可能会想通过一个全局变量来接收ret
这样,程序确实能得到异步操作的结果
但是这种方法肯定是无意义的
4、回调函数
在这种情况下,我们可以通过一个回调函数来完成这一需求
即,在定义函数时,多加一个函数(通常将函数名命名为callback)作为其参数,如下
function add(x,y,callback){
console.log(1)
setTimeout(function(){
var ret = x + y
callback(ret)
},1000)
}
add(10,20,function(data){
console.log(data)
})
这样,就会将异步操作中的 ret 作为回调函数的参数去执行,从而从外部得到异步操作的结果
5、封装原生Ajax方法
相信大家都用过jquery封装的ajax方法发送异步请求,
如 $.get(‘xxxx’,function(){ }) 。大家就会发现往往异步操作都会伴有一个回调函数
所以我们从来没见过这样的操作吧 var ret = $.get()
现在为了更好的了解底层,我们可以自己尝试着封装原生的JS的ajax
首先,原生的JS的ajax
a.txt中的文件内容
在控制台中查看结果
现在我们通过callback进行封装自己的ajax方法
<script type="text/javascript">
function get(url,callback){
var oReq = new XMLHttpRequest()
//当请求加载成功后调用此函数
oReq.onload = function(){
callback(oReq.responseText)
}
oReq.open('get',url,true)
oReq.send()
}
//调用
get('./a.txt',function(data){
console.log(data)
})
</scrpit>
6、Promise
6.1 应用场景
如果有多个异步操作,是无法保证其输出顺序的,如
//index.js
let fs = require('fs')
fs.readFile('./a.txt',function(err,data){
if(err){
console.log('读取文件失败')
}
console.log(data.toString()) //由于读取出来的文件是二进制文件,所以如果需要显示出来得使用 toString() 方法
})
fs.readFile('./b.txt',function(err,data){
if(err){
console.log('读取文件失败')
}
console.log(data.toString()) //由于读取出来的文件是二进制文件,所以如果需要显示出来得使用 toString() 方法
})
a和b的读取顺序是不定的
所以现在有一个需求就是让b在a之后执行
可能会想到通过嵌套的方式,如
这样的确可以保证输出顺序,但是如果代码一多,就不便于维护,进入了回调地狱
一个有意思的回调地狱的图片:
6.2 Promise完成回调嵌套
定义:Promise 首先是一个容器,里面存放了一个异步任务,异步任务的状态分为 resolved 和rejected
通过Promise优化代码
首先,创建两个promise对象,里面分别装读取a和b的文件操作
let fs = require('fs')
//new一个Promise对象
var p1 = new Promise(function(resolve,reject){
fs.readFile('./a.txt',function(err,data){
if(err){
//将容器中的任务状态变为rejected
reject(err)
} else{
resolve(data)
}
})
})
var p2 = new Promise(function(resolve,reject){
fs.readFile('./b.txt',function(err,data){
if(err){
//将容器中的任务状态变为rejected
reject(err)
} else{
resolve(data)
}
})
})
通过promise对象的 then 方法进行调用异步操作,then方法中传递两个函数参数
第一个函数参数是异步操作成功后,进行的操作,对应resolve(data),第二个则是失败时进行的操作(可以省略第二个参数),对应reject(err),如
p1.then(function(data){
console.log(data.toString())
},function(err){
console.log(err)
})
此时,程序输出
现在要实现嵌套操作,只需在第一个函数体中 return 另一个promise对象,然后接着调用 then 方法,如
p1
.then(function(data){
console.log(data.toString())
return p2
})
.then(function(data){
console.log(data.toString())
})
此时就可以完成需求
6.3 Promise封装
可以将这个代码段进行封装,方便重复使用,如下
let fs = require('fs')
//封装
function pReadFile(path){
return new Promise(function(resolve,reject){
fs.readFile(path,'utf8',function(err,data){
if(err){
reject(err)
} else{
resolve(data)
}
})
})
}
//调用
pReadFile('./a.txt')
.then(function(data){
console.log(data)
return pReadFile('./b.txt')
})
.then(function(data){
console.log(data)
})
2019.4.3 23:30