【ES6标准入门】JavaScript的async的语法和应用,异步操作思想之一(高级)

在这里插入图片描述

😁 作者简介:一名大四的学生,致力学习前端开发技术
⭐️个人主页:夜宵饽饽的主页
❔ 系列专栏: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的改进体现在以下四点

  1. 内置执行器
  2. 更好的语义
  3. 更广的适用性:async函数的await命令后面,可以是Promise对象和原始类型的值(数值,字符串,布尔值,但这时等于同步操作)
  4. 返回值是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)
})

📝 使用细节:

  1. 由于async函数返回的是Promise对象,可以作为await命令参数,所以,上面的例子也可以写成下面的形式

  2. 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)
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值