ES6 中的 Async 函数

发布于 2024-09-19 18:31:48 字数 4891 浏览 13 评论 0

async 函数是 Generator 函数的语法糖,它将 Generator 函数的 星号* 替换成 async ,将 yield 替换成 await

async 函数对 Generator 函数的改进:

  1. 内置执行器
  2. 更好的语义
  3. 更广的适用性

    co 模块约定,yield 命令后面只能是 Thunk 函数或 Promise 对象,而 async 函数的 await 命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)

  4. 返回值是 Promise

    async 函数的返回值是 Promise 对象 ,这比 Generator 函数的返回值是 Iterator 对象 方便多了。你可以用 then 方法指定下一步的操作。

    进一步说,async 函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而 await 命令就是内部 then 命令的语法糖

基本用法

// 指定 50 毫秒以后,输出 hello world
function timeout(ms) {
    return new Promise((resolve) => {
        setTimeout(resolve, ms);
    });
}
async function asyncPrint(value, ms) {
    await timeout(ms);
    console.log(value);
}
asyncPrint('hello world', 50);
  • async 函数返回一个 Promise 对象,内部 return 语句返回的值,会成为 then 方法回调函数的参数。async 函数内部抛出错误,会导致返回的 Promise 对象变为 reject 状态 。抛出的错误对象会被 catch 方法回调函数接收到。

    await 命令后面的 Promise 对象如果变为 reject 状态,则 reject 的参数会被 catch 方法的回调函数接收到

  • async 函数返回的 Promise 对象,必须等到内部所有 await 命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到 return 语句或者抛出错误 。也就是说,只有 async 函数内部的异步操作执行完,才会执行 then 方法指定的回调函数
  • 正常情况下,await 命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。

    另一种情况是,await 命令后面是一个 thenable 对象(即定义 then 方法的对象),那么 await 会将其等同于 Promise 对象。

  • 借助 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);
        }
    }
    one2FiveInAsync();
    
  • 任何一个 await 语句后面的 Promise 对象变为 reject 状态,那么整个 async 函数都会中断执行 。

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

    另一种方法是 await 后面的 Promise 对象再跟一个 catch 方法 ,处理前面可能出现的错误

  • 多个 await 命令后面的异步操作,如果不存在继发关系,最好让它们同时触发
  • await 命令只能用在 async 函数之中,如果用在普通函数,就会报错
    async function dbFuc(db) {
        let docs = [{}, {}, {}];
        // 报错
        docs.forEach(function (doc) {
            await db.post(doc);
        });
    }
    
    //上面代码可能不会正常工作,原因是这时三个 db.post 操作将是并发执行,也就是同时执行,而不是继发执行。正确的写法是采用 for 循环。
    async function dbFuc(db) {
        let docs = [{}, {}, {}];
        for (let doc of docs) {
            await db.post(doc);
        }
    }
    
    // 另一种方法是使用数组的 reduce 方法
    // 下面 reduce 方法的第一个参数是 async 函数,导致该函数的第一个参数是前一步操作返回的 Promise 对象,所以必须使用 await 等待它操作结束。另外,reduce 方法返回的是 docs 数组最后一个成员的 async 函数的执行结果,也是一个 Promise 对象,导致在它前面也必须加上 await
    async function dbFuc(db) {
        let docs = [{}, {}, {}];
        await docs.reduce(async (_, doc) => {
            await _;
            await db.post(doc);
        }, undefined);
    }
    
  • 希望多个请求并发执行,可以使用 Promise.all 方法。当三个请求都会 resolved 时,下面两种写法效果相同
    async function dbFuc(db) {
        let docs = [{}, {}, {}];
        let promises = docs.map((doc) => db.post(doc));
        let results = await Promise.all(promises);
        console.log(results);
    }
    // 或者使用下面的写法
    async function dbFuc(db) {
        let docs = [{}, {}, {}];
        let promises = docs.map((doc) => db.post(doc));
        let results = [];
        for (let promise of promises) {
            results.push(await promise);
        }
        console.log(results);
    }
    
  • async 函数可以保留运行堆栈
    const a = () => {
        b().then(() => c());
    };
    

    上面代码中,函数 a 内部运行了一个异步任务 b()。当 b() 运行的时候,函数 a() 不会中断,而是继续执行。等到 b() 运行结束,可能 a() 早就运行结束了,b() 所在的上下文环境已经消失了。如果 b() 或 c() 报错,错误堆栈将不包括 a()。

    const a = async () => {
        await b();
        c();
    };
    
    

    上面代码中,b() 运行的时候,a() 是暂停执行,上下文环境都保存着。一旦 b() 或 c() 报错,错误堆栈将包括 a()。

实例:按顺序完成异步操作

依次远程读取一组 URL,然后按照读取的顺序输出结果 :

async function logInOrder(urls) {
    for (const url of urls) {
        const response = await fetch(url);
        console.log(await response.text());
    }
}

上面代码确实大大简化,问题是所有远程操作都是继发。只有前一个 URL 返回结果,才会去读取下一个 URL ,这样做效率很差,非常浪费时间。我们需要的是并发发出远程请求 。

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);
    }
}

上面代码中,虽然 map 方法的参数是 async 函数,但它是并发执行的,因为只有 async 函数内部是继发执行,外部不受影响。后面的 for..of 循环内部使用了 await,因此实现了按顺序输出。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

沧桑㈠

暂无简介

文章
评论
26 人气
更多

推荐作者

singlesman

文章 0 评论 0

も星光

文章 0 评论 0

抽个烟儿

文章 0 评论 0

年少掌心

文章 0 评论 0

qq_wsqNUx

文章 0 评论 0

南薇

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文