码字不易,转发请注明原文链接
前言
了解js的小伙伴们知道,js是一门单线程语言。所以js要实现“异步”操作,就需要一些奇淫异巧,此三种是较为常用的js异步操作处理技巧。在本文中我会先介绍这三种的定义与用法,其次会比较三者之间的联系。在以下的例子中我会用到setTimeout函数来模拟异步。
Promise
说起Promise依稀还记得当时初学微信小程序,异步调用后台数据被他血虐的情形。在将其弄懂之后,至今一直在用Promise来处理js异步调用,直到最近才入坑async(可见Promise的强大,其实自己菜鸡 >_<)。在Promise之前,要用js进行异步调用需要用到回调函数这哥们。我们先来瞅瞅回调函数是如何处理js异步调用的:
//例1:
function sleep(second,showTime){
setTimeout(()=>{
showTime()
if(second<1){
console.log("间隔时间太短了!!!")
}else if(second<2){
console.log(second+"秒非常好")
}else{
console.log(second+"秒有点长")
}
},second*1000)
}
function showTime(){
console.log("运行showTime了哦,"+ new Date())
}
sleep(2,showTime)
console.log("我不管,我先到的,"+ new Date())
在当前浏览器按F12,将以上代码复制到js终端(Console)敲回车(以后都能如此运行代码,偷懒一下,当然也可以用node.js运行),运行后发现showTime()函数晚2秒钟执行。大功告成!!!那是不是这样就足够了呢,显然不是,客户的需求永远都是奇形怪状的,如果有多层异步操作嵌套怎么办?如果继续用函数回调,则代码形式如下:
//例2:
sleep(1,()=>{
showTime()
sleep(2,()=>{
showTime()
sleep(3,()=>{
showTime()
})
})
})
console.log("我不管,我先到的,"+ new Date())
代码看着感觉非常的酸爽,可能写的时候非常爽,但是到了后期维护的时候看到这种代码想屎的心都有了。。。这也是回调函数的弊端。正是回调函数有了这种弊端,所以在es6中引入了Promise对象,我们先看下Promise是如何处理例1的。
function sleep(second,showTime){
return new Promise(function(resolve,reject){
setTimeout(()=>{
showTime()
if(second<1){
reject("间隔时间太短了!!!")
}else if(second<2){
resolve(second+"秒非常好")
}else{
resolve(second+"秒有点长")
}
},second*1000)
})
}
function showTime(){
console.log("运行showTime了哦,"+ new Date())
}
sleep(2,showTime).then(res=>{console.log(res)}).catch(err=>{console.log(err)}) //输出“时间非常好”
console.log("我不管,我先到的,"+ new Date())
sleep(0.2,showTime).then(res=>{console.log(res)}).catch(err=>{console.log(err)}) //输出“间隔时间太短了!!!”
以上代码运行一下就能对Promise有个大概的认识。此方法是通过返回Promise对象,然后通过then与cathch方法获得异步调用中的值,需要注意的是then和catch方法返回的也是Promise对象。具体就是在sleep函数中返回了一个Promise初始化对象,初始化参数是一个回调函数,此回调函数又有两个参数,分别是resolve和reject(这两货也是回调函数)。resolve用于返回成功状态时的值,用Promise对象的then函数获取。reject用于返回失败状态的值,用Promise对象的catch函数获取。
接下来可以利用Promise来实现例2的异步调用:
function sleep(second,showTime){
return new Promise(function(resolve,reject){
setTimeout(()=>{
showTime()
if(second<1){
reject("间隔时间太短了!!!")
}else if(second<2){
resolve(second+"秒非常好")
}else{
resolve(second+"秒有点长")
}
},second*1000)
})
}
sleep(1,showTime).then(res=>{
console.log(res);
return sleep(2,showTime);
}).then(res=>{
console.log(res);
return sleep(3,showTime);
}).then(res=>{
console.log(res);
return sleep(0.3,showTime);
}).catch(err=>{
console.log(err);
})
相比于回调函数看起来更加的清晰,更加的有条理,装b的说就是就是相较于回调函数,可以避免进入“回调地狱”。更具体的Promise对象描述可在MDN上学习,传送门。
generator
在介绍async之前,还是想先介绍下generator,毕竟阮一峰大佬曾说过:“async 函数就是 Generator 函数的语法糖”。其实generator也是在Promise的基础上实现异步调用的。只不过并不是利用Promise中的then和catch方法来获取异步处理后的值,代码更偏向于同步的形式。generator是通过function* 来定义的,并且在函数中不断通过yield关键字来多次返回值,并通过next函数获取每次返回的值。通过例2代码实现如下:
function sleep(second,showTime){
return new Promise(function(resolve,reject){
setTimeout(()=>{
showTime(second)
if(second<1){
reject("间隔时间太短了!!!")
}else if(second<2){
resolve(second+"秒非常好")
}else{
resolve(second+"秒有点长")
}
},second*1000)
})
}
function* gen(){
let s3 = yield sleep(9,showTime);
let s2 = yield sleep(5,showTime);
let s1 = yield sleep(2,showTime);
console.log("s1"+s1);
console.log("s2"+s2);
console.log("s3"+s3);
}
let g = gen();
g.next(); //触发s3
g.next(); //触发s2
g.next(); //触发s1
g.next(); //出发剩余操作,三个console操作。
console.log("我不管,我先到的,"+ new Date())
每运行一个next函数,都会依次触发yield 获取一个返回值。后续4个g.next()操作时间间隔非常短,远小于2秒,所以会发现三个console操作会先打出来,然后0秒过后会返回是s1,再3秒过后会返回s2,再4秒过后会返回s3.
具体有关generator的用法可参考廖雪峰大佬的教程,传送门
async
在介绍完generator后,感觉还是比较麻烦,每次调用异步程序都得去解析next(),并且如果在返回s2的过程中需要s3返回值的话,用generator是比较麻烦的。所以能不能有一个更好的办法呢?庆幸的是,es7中引入了async和await函数,完美的解决了以上顾虑,利用async解决例2问题如下:
async function gen_async(){
let s1 = await sleep(1,showTime);
let s2 = await sleep(2,showTime);
let ss = await sleep(0.5,showTime);
let s3 = await sleep(3,showTime);
console.log("s1:"+s1);
console.log("s2:"+s2);
console.log("s3:"+s3);
}
try{
gen_async()
}catch{
con
}
运行下代码会发现这已经非常接近同步的形式了。而且与自己心中想的是一样的,没有回调函数的回调地狱,也不会由then的链式调用,更不会有next的看似冗余操作。形式和同步操作一样,并且真能解决异步操作,目前为止算是最好的方法了。但是需要注意的一点就是,代码在返回ss时报错,async不会捕获异常,所以会终止程序,导致无法返回s3,所以我们在用async时需要记住用try…catch…来手动捕获异常,如下所示:
async function gen_async(){
try{
let s1 = await sleep(1,showTime);
let s2 = await sleep(2,showTime);
let ss = await sleep(0.5,showTime);
let s3 = await sleep(3,showTime);
console.log("s1:"+s1);
console.log("s2:"+s2);
console.log("s3:"+s3);
}catch{
console.log("有异常啦,快去处理吧")
}
}
gen_async()
另外还需要注意的是await只能在申明了async的函数下使用,具体详情可参考阮一峰的教程,传送门