LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

详解JavaScript异步编程之async和await

admin
2024年4月1日 15:5 本文热度 700

经过了Generator的过渡之后异步代码同步化的需求逐渐成为了主流需求,虽然Generator函数能够实现异步编程,但实际上我们很少用它来实现异步,因为在ES7版本中得到了提案,并在ES8版本中进⾏了实现的 async 函数对Generator函数的流程又做了一层封装,定义了全新的异步控制流程,使得异步方案使用更加方便。


01
介绍


async function foo(){
 await ...
 await ...
}
foo()

async/await的代码结构的编写⽅式与Generator函数结构很相似,async就相当于那个(*),await就相当于yield。提案中规定了可以使⽤async修饰⼀个函数,这样就能在该函数的直接⼦作⽤域中,使⽤await来⾃动的控制函数的流程,await 右侧可以编写任何变量或对象,当右侧是普通对象的时候函数会⾃动返回右侧的结果并向下执⾏,⽽当await右侧为Promise对象时,如果Promise对象状态没有变成完成,函数就会挂起等待,直到Promise对象变成fulfilled,程序再向下执⾏,并且Promise的值会⾃动返回给await左侧的变量中。async和await需要成对出现,async可以单独修饰函数,但是await只能在被async修饰的函数中使⽤。


有了await和async就相当于使⽤了⾃带执⾏函数的Generator函数,这样我们就不再需要单独针对Generator函数进⾏开发了,所以async和await逐渐成为主流异步流程控制的终极解决⽅案。⽽Generator慢慢淡出了业务开发者的舞台,不过Generator函数成为了向下兼容过渡期版本浏览器的候补实现⽅式,虽然在现今的⼤部分项⽬业务中使⽤Generator函数的场景⾮常的少,但是如果查看脚⼿架项⽬中通过babel构建的JavaScript⽣产代码,我们还是能⼤量的发现Generator的应⽤的,它的作⽤就是为了兼容不⽀持async和await的浏览器。


02
介绍async


async的英文意思是异步,当函数前面有async关键字并且该函数有返回值时,函数执行成功,函数就会调用Promise.resove()并隐式的返回一个Promise对象;如果函数执行失败就会调用Promise.reject()并返回一个Promise对象。

async function foo(){
  return 1
}
let res = foo()
console.log(res)

根据控制台结果我们发现其实async修饰的函数,本身就是⼀个Promise对象,虽然我们在函数中return的值是1,是使⽤了async修饰之后,这个函数运⾏时并没有直接返回1,⽽是返回了⼀个值为1的Promise对象。


async函数中如果有异步操作会进行等待,但是async函数本身会马上返回,不会阻塞当前线程。async函数被调用不会阻塞界面渲染,内部由await关键字修饰异步过程,会阻塞等待异步任务的完成再返回。

如果在函数中return一个直接量,async会把这个直接量通过Promise.resolve(直接量) 封装成 Promise 对象 ,如果没有返回值,相当于返回了Promise.resolve(undefined)。

我们可以通过Promise.then()回调得到async函数的返回值,因为该函数返回的是Promise对象。

async function foo(){
  return 1
}
let res = foo()
console.log(res)

foo().then((res) => {
  console.log('res的值是', res)
})

只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。


看下面代码:

async function foo(){
    console.log(3)
    return 1;
}
console.log(1)
foo()
console.log(2)

通过控制台看到打印顺序为1、3、2。按照Promise对象的执⾏流程function被async修饰之后它本身应该变成异步函数,那么它应该在1和2输出完毕之后再输出3,但是结果却出⼈意料,这是为什么呢?

我们回想一下Promise函数的结构。

new Promise(function(){
    
}).then(function(){

})

在介绍Promise对象时,我们知道new Promise时的function是同步流程,而then()是异步的,这也就不难解释为什么输出结果是1、3、2了。


03
介绍await


await的英文意思是等待,等待的是一个表达式,这个表达式的计算结果是 Promise 对象或者其它值,得到resolve的值作为await表达式的运算结果。

因为 async 函数返回一个 Promise 对象,所以 await 可以用于等待一个 async 函数的返回值,这也可以说是 await 在等 async 函数,实际上它等待的是一个返回值。await 不仅仅用于等 Promise 对象,它可以等任意表达式的结果。


await 表达式的运算结果取决于它等的东西。如果它等到的不是一个 Promise 对象,相当于 await Promise.resolve(...),那 await 表达式的运算结果就是它等到的东西。

如果它等到的是一个 Promise 对象,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。

依然以上面的代码为例,稍加改造。

async function foo(){
  console.log(3);
  var a = await 4;
  console.log(a);
  return 1;
}

console.log(1)
foo()
console.log(2)

通过控制台我们可以看到打印顺序为1->3->2->4。可以看到await 4表达式会将4作为其运算结果赋值给a,并且await会阻塞后面的console.log(a)的执行,所以最后才会打印出4,这就是 await 必须用在 async 函数中的原因。

我们将上面的函数翻译⼀下,由于async修饰的函数会被解释成Promise对象,所以我们可以将其翻译成如下结构:

console.log(1)
new Promise(function(resolve){
    console.log(3)
    resolve(4)
}).then(function(a){
    console.log(a)
})
console.log(2)

看到这个Promise对象我们就明白了,由于初始化的回调是同步的所以1,3,2都是同步代码,⽽4是在resolve中传⼊的,then代表异步回调所以4应该最后输出。

综上所述,async函数中有⼀个最大的特点,就是第⼀个await会作为分⽔岭⼀般的存在,在第⼀个await的右侧和上⾯的代码,全部是同步代码区域相当于new Promise的回调,第⼀个await的左侧和下⾯的代码,就变成了异步代码区域相当于then的回调。


04
await的优势


假设一个业务,分多个步骤完成,每个步骤都是异步的,而且依赖于上一个步骤的结果。在过去的编程中JavaScript的主要异步处理⽅式,是采⽤回调函数的⽅式来进⾏处理,想要保证n个步骤的异步编程有序进⾏,会出现如下的代码。

setTimeout(function(){
  //第⼀秒后执⾏的逻辑
  console.log('第⼀秒之后发⽣的事情')
  setTimeout(function(){
    //第⼆秒后执⾏的逻辑
    console.log('第⼆秒之后发⽣的事情')
      setTimeout(function(){
      //第三秒后执⾏的逻辑
      console.log('第三秒之后发⽣的事情')
    },1000)
  },1000)
},1000)

虽然可以Promise 通过 then 链来解决多层回调的问题,但是现在有了async/await我们可使用它来进一步优化上面的代码。

// 假设起始值为1,每个步骤都是异步的,而且依赖于上一个步骤的计算结果,且每间隔一秒打印结果
function runTask(n) {
    return new Promise(resolve => {
        setTimeout(() => resolve(n + 1), 1000);
    });
}

function task1(n) {
    console.log(`step1 with ${n}`);
    return runTask(n);
}

function task2(n) {
    console.log(`step2 with ${n}`);
    return runTask(n);
}

function task3(n) {
    console.log(`step3 with ${n}`);
    return runTask(n);
}

用 Promise 方式来实现这三个步骤。

function run() {
    const sum1 = 1;
    task1(sum1)
        .then(sum2 => task2(time2))
        .then(time3 => task3(time3))
        .then(result => {
            console.log(result) // 4
        });
}
run();

如果用 async/await 来实现如下。

async function run() {
    const sum1 = 1;
    const sum2 = await task1(sum1);
    const sum3 = await task2(sum2);
    const result = await task3(sum3);
    console.log(`result is ${result}`); // result is 4
}

run();

现在我们可以使⽤如上的⽅式来进⾏流程控制,不再需要依赖自己定义的流程控制器函数来进⾏分步执⾏,这⼀切的核⼼起源都是Promise对象的规则定义开始的。使用async/await结果和之前的 Promise 实现是一样的,但是这个代码看起来是不是清晰得多,几乎跟同步代码一样。


05
注意事项


1、await只能用在async函数之中,也就是说await必须和async一起使用,反之,async可以单独只用。


2、await后面跟着是一个Promise对象,会等待Promise返回结果了,再继续执行后面的代码。

const timer = n => {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            console.log('1')
            resolve();
        }, n);
    })
};
const run = async () => {
    await timer(3000);
    console.log('2');
};
run()
// 等待3s之后,分别打印出1、2


3、await后面跟着的是一个数值或者是字符串等数据类型的值,则直接返回该值。


4、await后面跟着的是定时器,不会等待定时器里面的代码执行完,而是直接执行后面的代码,然后再执行定时器中的代码。

const run = async () => {
    console.log(1)
    await setTimeout(() => {
        console.log(2)
    }, 1000);
    console.log(3);
};
run()
// 1 -> 3 -> 2


5、可以直接用标准的try...catch...语法捕捉错误。

async function asyncFunction() {
    throw new Error('Something went wrong');
}

const run = async() => {
    try {
        await asyncFunction();
    } catch (error) {
        console.error('Caught an error:', error);
    }
}
run()


06
总结


从回调地狱到Promise的链式调⽤到Generator函数的分步执⾏再到async和await的⾃动异步代码同步化机制,经历了很多个年头,所以⾯试中为什么经常问到Promise,并且重点沿着Promise对象深⼊的挖掘去问你各种问题,主要是考察程序员对Promise对象本身以及他的发展历程是否有深⼊的了解,同时也是在考察⾯试者对JavaScript的事件循环系统和异步编程的基本功是否⾜够的扎实。Promise和事件循环系统并不是JavaScript中的⾼级知识,⽽是真正的基础知识,所以所有⼈想要在⾏业中更好的发展下去,这些知识都是必备基础,必须扎实掌握。


该文章在 2024/4/1 15:05:16 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2024 ClickSun All Rights Reserved