JavaScript 知识碎片[四] async / await

本文详细介绍了JavaScript中的async/await语法,包括它们如何作为Generator函数的改进,与Promise的交互,以及如何处理异步错误。通过实例展示了async函数的使用方式,如在循环中的应用,以及如何利用try...catch处理错误。还探讨了async/await相对于Promise的优势,如更清晰的代码结构和友好的错误处理。
摘要由CSDN通过智能技术生成

业精于勤 荒于嬉

async await 是什么?

async 函数是对Generator 函数的改进,也就是generator 函数的语法糖,它建立在Promise 上,并且与所有现有的基于Promise 的 API 兼容。

1、async 声明一个异步函数(async function someName(){....})

2、自动将常规函数转换为Promise,返回值也是一个Promise对象

3、只有 async 函数 内部的异步操作执行完,才执行 then 方法制定的回调函数

4、异步操作内部可以使用 await 

         ①、await ..... 暂停异步操作的功能执行 (let result = await someAsyncCall())

         ②、防止在 Promise 调用之前,await 强制其他代码等待,直到 Promise 完成并返回结果

         ③、只能与Promise 一起使用,不适用于回调

         ④、只能在 async 函数内部使用

理解实践

由于async 函数返回的是 Promise 对象,可以作为 await 命令的参数

    function timeout(ms) {
            return new Promise((resolve => {
                // setTimeout 函数 代表的就是 ms 时间之后执行 resolve 
                setTimeout(resolve, ms)
            }))
        }

        async function asyncPrint(value, ms) {
            await timeout(ms)
            console.log(value)
        }
        asyncPrint('都是套路', 50).then(() => {
            console.log("测试一下")
        })

async 函数内部的return 语句 返回值 会成为 then 方法回调函数的参数


        async function f() {
            return 'hello World'
        }
        f().then(v => console.log(v))

async 函数内部抛出错误,会导致返回的Promise 对象变为 reject 状态,抛出的错误对象会被catch 方法回调函数接收到

       async function ff() {
            throw new Error('脑子有问题了')
        }
        ff().then(v => console.log("resolve", v), e => console.log('reject', e)).catch(err => console.log('catch::err',
            err))
        ff().then(v => console.log("resolve", v)).catch(err => console.log('catch::err',
            err))

 Promise 对象的状态变化:async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。

    async function getTitle(url) {
            let response = await fetch(url)
            let html = await response.text();
            return html.match(/<title>([\s\S]+)<\/title>/i)[1]
        }
        getTitle('https://tc39').then(r => console.log('r::', r)).catch(err => console.log('err::', err))
 // await 命令
        function sleep(interval) {
            return new Promise(resolve => {
                setTimeout(resolve, interval)
            })
        }
        // 使用
        async function one2fiveInAsync() {
            for (let i = 1; i <= 5; i++) {
                console.log(i)
                await sleep(1000)
            }
            return 'haha'
        }
        one2fiveInAsync().then(val => {
            console.log('one2fiveInAsync::', val)
        })

        // await命令后面的 Promise 对象如果变为reject状态,则reject的参数会被catch方法的回调函数接收到
        async function fff() {
            await Promise.reject('出错了')
        }
        fff()
            .then(v => console.log(v))
            .catch(e => console.log(e))

        // 任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行。
        async function ffff() {
            await Promise.reject('出错了');
            await Promise.resolve('hello world'); // 不会执行
        }

 有时,我们希望即使前一个异步操作失败,也不要中断后面的异步操作。这时可以将第一个await放在try...catch结构里面,这样不管这个异步操作是否成功,第二个await都会执行。

      async function f2() {
            try {
                await Promise.reject('出错了')
            } catch (e) {
                console.log("ee::", e)
            }
            return await Promise.resolve('hello world f2')
        }
        f2().then(v => console.log(v))
        // 另一种方法是await后面的 Promise 对象再跟一个catch方法,处理前面可能出现的错误。
        async function f3() {
            await Promise.reject('f3 出错了').catch(e => {
                console.log(e)
            });
            return await Promise.resolve('hello world f3')
        }
        f3().then(v => console.log(v))

如果有多个await命令,可以统一放在try...catch结构中。

     function getFoo() {
            console.log('getFoo')
        }

        function getBar() {
            console.log('getBar')
        }

        async function f4() {
            // 多个await 命令后面的异步操作,如果不存在继发关系,最好让他们
            // 同事触发
            // let foo1 = await getFoo()
            // let bar1 = await getBar()
            // 像以上代码 getFoo 和 getBar 是两个独立的异步操作(即互不依赖),被写成继发关系。这样比较耗时,因为只有getFoo 完成以后,才会执行getBar,完全可以让它们同时触发。

            // // 写法一
            // let [foo2, bar2] = await Promise.all([getFoo(), getBar()])

            // // 写法二
            let fooPromise = getFoo()
            let barPromise = getBar()
            let foo3 = await fooPromise;
            let bar3 = await barPromise;
        }
        f4()

await 只能用在async 函数 ,但是如果将forEach方法的参数 改成 async 函数,也有问题

      function dbFuc(db) {
            let docs = [{}, {}, {}];

            //可能得到错误结果
            docs.forEach(async function (doc) {
                await db.post(doc)
            })
        }

        // 上面代码可能不会正常工作,原因是这时三个db.post()操作将是并发执行
        // 而不是继发执行 正确的写法应该采用for 循环
        async function dbFun(db) {
            let docs = [{}, {}, {}];
            for (let doc of docs) {
                await db.post(doc)
            }
        }

async/await 相比于 Promise 的 优势

  • 代码读起来更加同步,Promise虽然摆脱了回调地狱,但是then的链式调⽤也会带来额外的阅读负担
  • Promise传递中间值非常麻烦,而async/await几乎是同步的写法,非常优雅
  • 错误处理友好,async/await可以使用成熟的try/catch,Promise的错误捕获非常冗余
  • 调试友好,Promise的调试很差,由于没有代码块,你不能在⼀个返回表达式的箭头函数中设置断点,如果你在一个.then代码块中使⽤调试器的步进(step-over)功能,调试器并不会进入后续的.then代码块,因为调试器只能跟踪同步代码的『每一步』。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值