😁 作者简介:一名大四的学生,致力学习前端开发技术
⭐️个人主页:夜宵饽饽的主页
❔ 系列专栏:JavaScript进阶指南
👐学习格言:成功不是终点,失败也并非末日,最重要的是继续前进的勇气
🔥前言:
这是关于async的语法和应用,这是异步操作的最终确定的方案,也是目前最好的解决方案。 这是我自己的学习JavaScript的笔记,希望可以帮助到大家,欢迎大家的补充和纠正
🌻 理解异步操作的中级篇在上一节:JavaScript的Generator函数的异步应用,异步操作思路之一(中级)
文章目录
第18章 async函数
18.1 含义
ES2017标准引入了async函数,使得异步操作变得更加方便,async函数是什么?
用一句话来说,它就是Generator函数的语法糖
var asyncReadFile=async function(){
var f1=await readFile('/etc/fstab')
var f2=await readFile('/etc/shells')
console.log(f1.toString())
console.log(f2.toString())
}
通过比较就会发现async函数就是将Generator函数的星号(*)替换为async,将yield替换成await,仅此而已
async函数对Generator的改进体现在以下四点
- 内置执行器
- 更好的语义
- 更广的适用性:async函数的await命令后面,可以是Promise对象和原始类型的值(数值,字符串,布尔值,但这时等于同步操作)
- 返回值是Promise
18.2 用法
async函数返回一个Promise对象,可以使用then方法添加回调函数,当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体后面的语句
async function getStockPriceByName(name){
var symbol=await getStockSymbol(name)
var stockPrice=await getStockPrice(symbol)
return stockPrice
}
getStockPriceByName('goog').then(function(result){
console.log(result)
})
📝 使用细节:
-
由于async函数返回的是Promise对象,可以作为await命令参数,所以,上面的例子也可以写成下面的形式
-
async函数有多种使用形式
//函数声明 async function foo(){} //函数表达式 const foo=async function(){} //对象的方法 let obj={ async foo() } obj.foo().then(...); //Class方法 class Storage{ async getAcatar(name){} }
18.3 语法
18.3.1 返回Promise对象
1. async函数内部return语句返回值,会成为then方法回调函数的参数
async function f(){
return 'hello world'
}
f().then(v => concole.log(v))
//hello world
2. async函数内部抛出错误会导致返回的Promise对象变为reject状态,抛出的错误对象会被catch方法回调函数接收到
18.3.2 Promise对象的状态变化
async函数返回的Promise对象必须等到内部所有await命令后面的Promise对象执行完才会发生状态改变,除非遇到return语句或者抛出错误,也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数
18.3.3 await命令
1. await命令后面是一个Promise对象,如果不是,会被转成一个立即resolve的Promise对象
2. await命令后面的Promise对象如果变为reject状态,则reject的参数会被catch方法的回调函数接收到,整个async函数都会中断执行
⭐️ 3. 有时,我们希望即使前一个异步操作失败,也不要中断后面的异步操作,这时候有两种解决方案
-
将第一个await放在try…catch结构里面,这样不管这个异步操作是否成功,第二个await都会执行
async function f(){ try{ await Promise.reject('出错了') }.catch(e){ } return await Promise.resolve('hello world') } f() .then(v => consoel.log(v)) //hello world
-
另一种方法是在await后面的Promise对象后添加一个catch方法,处理前面可能出现的错误
async function f(){ await Promise.reject('出错了') .catch(e => console.log(e)) return await Promise.resolve('hello world') } f() .then(v => console.log(v)) //出错了 //hello world
18.3.4 错误处理
如果是单个错误处理,可以使用上一节的错误处理的方式,但是如果有多个await命令,则统一放在try…catch结构中
async function main(){
try{
var val1=await firstStep()
var val2=await secondStep(val1)
var val3=await thirdStep(val1,val2)
console.log('Final:',val3)
}
catch(err){
console.error(err)
}
}
18.3.5 使用注意点
1.前面已经说过了,await命令后面的Promise对象的运行结果可能是rejected,所以最好把await命令放在try…catch代码块中
2.多个await命令后面的异步操作如果不存在继发关系,最好让它们同时触发
let foo=await getFoo()
let bar=await getBar()
上面代码中,getFoo和getBar是两个独立的异步操作被写成继发关系,这样比较耗时,因为只有getFoo完成以后才会执行getBar,完全可以让它们同时触发
//写法一
let [foo,bar]=await Promise.all([getFoo(),getBar()])
//写法二
let fooPromise=getFoo()
let barPromise=getBar()
let foo=await fooPromise
let bar=await barPromise
3.await命令只能用在async函数之中,如果用在普通函数中就会报错。
在这一点中,有一个特别的情况,⭐️ 如果将forEach方法的参数改成async函数,也会出现问题
function dbFun(db){
let docs=[{},{},{}]
//可能得到得到错误结果
docs.forEach(async function (doc){
await db.post(doc)
})
}
上面的代码可能不会正常工作,原因是这时的3个db.post操作将是并发执行,即同时执行,而不是继发执行,我们应该采用for循环
function dbFun(db){
let docs=[{},{},{}]
for(let doc of docs){
await db.post(docs)
}
}
如果我们确实希望多个请求并发执行,可以使用Promise.all方法。当3个请求都会resolved时,使用Promise.all和for…of效果会相同
18.4 async函数实现原理
async function fn(args){
//...
}
//等同于
function fn(args){
return spawn(function* (){})
}
function spawn(genF){
return new Promise(function(resolve,reject){
var gen=genF();
function step(nextF){
try {
var next=nextF()
} catch (error) {
return reject(e)
}
if(next.done){
return resolve(next.value)
}
Promise.resolve(next.value).then(function(v){
step(function(){return gen.next(v)})
},function(e){
step(function(){return gen.throw(e)})
})
}
step(function(){return gen.next(undefined)})
})
}
上述代码中,其中spawn函数就是自动执行器
18.5 其他异步处理方法的比较
Promise实现:相比回调函数的写法大大改进,但是代码完全是Promise的API,操作本身的语义反而不容易看出来
Generator实现:语义比Promise写法清晰,但是必须有一个任务运行器自动执行Generator函数
async实现:几乎没有与语义不相关的代码,将自动执行器改在语言层面,不暴露给用户,代码量更少
18.6 实例:按顺序完成异步操作
实际开发中经常遇到一组异步操作,需要按照顺序完成,比如,依次远程读取一组URL,然后按照读取的顺序输出结果
Promise写法:
function logInOrder(urls){
//远程读取所有URL
const textPromise=urls.map(url => {
return fetch(url).then(responese => respone.text())
})
}
//按次序输出
textPromise.reduce((chain,textPromise) => {
return chain.then(() => textPromise)
.then(text => console.log(text))
},Promise.resolve())
上面的代码使用fetch方法,同时远程读取一组URL.每个fetch操作都返回一个Promise对象,放入textPromises数组,然后,reduce方法依次处理每一个Promise对象,并且使用then将所有Promise对象连起来,因此就可以依次输出结果
async函数实现
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)
}
}