JavaScript异步函数async/await

9,960 阅读3分钟

这是我参与8月更文挑战的第14天,活动详情查看:8月更文挑战

异步函数是将期约应用于JavaScript函数的结果。异步函数可以暂停执行,而且不阻塞主线程。异步函数就是async/await,它是Es8新增的。

不知道异步的可以看这个理解异步 (juejin.cn)

async

async关键字用于声明异步函数,它可以在函数声明,函数表达式还有箭头函数上使用。

    async function msg(){}
    let jackson = async function(){}
    let jackson = async ()=>{}

使用async关键字可以让函数具有异步特征,在实际中它需要和await配合使用。

await

一旦定义了一个函数作为一个异步函数,我们就可以使用 await 关键词。这个关键词放在回调的Promise之前,将会暂停执行函数,直到Promise执行或拒绝。

 async function msg(){
        let p = new Promise((resolve,reject)=>setTimeout(resolve,1000,'jackson'));
        console.log(await p);
    }
    msg();//jackson

await 关键字会暂停执行异步函数后面的代码,它这个行为和生成器函数中的yield关键字是一样的,await关键字也是解包对象的值,任何将这个值传给表达式,再用异步恢复异步执行的操作。

停止和恢复执行

来个小栗子,大家看看能不能猜对执行操作,再从中理解一下。

    async function jackson(){
        console.log(await Promise.resolve('jackson'));
    }
    async function bear(){
        console.log(await 'bear');
    }
    async function daxiong(){
        console.log('daxiong');
    }
    jackson();
    bear();
    daxiong();

哈哈哈,其实是倒叙排列的。 image.png await关键字其实很简单,js运行在碰到await关键字时,会记录在哪里暂停执行。等到await右边的值可以使用了,就是处理完回调了,js会向消息列对中推送一个任务,这个任务会恢复异步函数的执行。这样的话,即使await后面跟着一个立即可用的值,函数的其余部分也会被异步求值。

异步函数并不能真正的替代Promise。但两个可以一起携手合作。一个异步函数将 await 执行一个Promise和一个异步函数始终返回一个Promise。

栈追踪和内存管理

期约和异步函数的功能差不多,但他们在内存中的表示差别很大。

    function fooPromiseExecutor(resolve, reject) {
        setTimeout(reject, 1000, 'jackson');
    }

    function foo() {
        new Promise(fooPromiseExecutor);
    }

    foo();

image.png 我们可以看到错误信息包含嵌套函数的标识符,那是被调用以创建最初期约实例的函数,其实函数已经返回了,因此栈追踪不应该看到他们。

js引擎会在创建期约时候尽可能保存完整的调用栈,在抛出错误的时候,调用栈可以由运行时的错误处理逻辑数据获取,因而就会出现在栈追踪信息中。这样肯定会占用更多的计算成本和内存。

把以上的例子换成异步函数,我们看一下

 function fooPromiseExecutor(resolve, reject) {
        setTimeout(reject, 1000,'jackson')
    }
    async function foo(){
        await new Promise(fooPromiseExecutor)
    }
    foo();

image.png 这样变成异步函数,栈追踪信息救能准确的反应当前的调用栈。fooPromiseExecutor已经返回,所以它不存在错误细腻些中。foo已经被挂起了,并没有退出。js在运行时可以简单嵌套函数中存储指向包含函数的指针,相当于同步函数调用栈一样,它不会像期约那样带来额外的消耗,结果不言而喻,我们在重视性能的时候可以有限考虑异步。