• Javascript中的await与async与promise存在什么关系?


    一、初识await与async

    初学到async与await时只是停留在表面,明白await是用来等待函数执行的,async是用来声明一个函数为异步函数的,并且只知道只有声明了async的函数,里面才可以用await。

    现在想起来,有一点没错,这两个的确是成对出现的。但是申明了async的函数是异步函数?这个是错的,申明了async的函数还是同步函数,因为我们知道,如果是异步函数,那么他的执行顺序肯定在同步函数之后:

    async function fun1(){
      console.log("我像是异步函数吗?")
    }
    
    function fun2(){
      console.log("我是同步函数")
    }
    fun1()
    fun2()
    console.log("666")

     所以申明async与异步函数并没有直接关系,想要知道为什么,继续看下面:

    先说一下async的声明语法:

    语法就是:

     如果没有声明async将会报错:

    二、async有什么作用

    它的全名是:await asynchronous(等待异步)

    1.先来看看例子:

    例子1:

    async function testAsync() {
        return "hello async";
    }
    
    const result = testAsync();
    console.log(result);
    c:var	est> node --harmony_async_await .
    Promise { 'hello async' }

    打印出来发现,申明了等待异步的函数,并不是直接返回一个我们想要的值,而是一个promise对象。原来是promise对象,那么就可以使用then链语法,一直then下去:

    例子1.2

    async function async1() {
      console.log("async1 start");
      console.log("async1 end");
    }
    let b =async1()
    console.log(b)

     如果async没有返回值,则直接返回一个undefined,且外面是一个promise包着

    例子2:

    先来看看第一个普通使用promise的例子:

    function conductStr(str){
      return new Promise((resolve)=>{
        resolve(str.substr(5).split('>'))
      })
    }
    
    //下面代码都是废话,主要看上面绿色区域
    let my_str = conductStr('当前位置:初三网 >肇庆中考 > 肇庆地区高中>肇庆市第四中学')
    my_str.then((response)=>{
      console.log(response)
    }).catch(err=>{
      console.log(err)
    })
     

    再来看看处理同一操作的async可以怎么写:

    async function conductStr(str){
        return ( str.substr(5).split('>'))
    
    }
    
    //同样下面是废话
    let my_str = conductStr('当前位置:初三网 >肇庆中考 > 肇庆地区高中>肇庆市第四中学')
    my_str.then((response)=>{
     console.log(response)
    }).catch(err=>{ console.log(err) })

    同样输出结果:

     我们可以看到,async申明的函数会把返回结果直接实例化一个new promise,然后通过resolve把参数传出去。

    所以总结一下:申明async函数的返回值会默认返回一个promise,这个返回值可以使用then链语法,一直写下去。

    带 async 关键字的函数,它使得你的函数的返回值必定是 promise 对象

    也就是

    如果async关键字函数返回的不是promise,会自动用Promise.resolve()包装

    如果async关键字函数显式地返回promise(即你自定义的promise),那就以你返回的promise为准

    三、await有什么作用

    await是用来等待后面的表达式执行完毕,才能继续执行下去,await后面的表达式可以是一个函数,也可以是一个普通表达式子

    看看例子:

    (1)异步方法1,2都用了await的情况:(注意,我所说的异步方法是指里面的settimeout,而不是外面的function,后面也是这样,而等待异步方法是指async函数,下面就不在继续提醒了。)

    function Async1() {
    return new Promise((resolve)=>{
    setTimeout(() => {
        return resolve("我是异步方法1");
      }, 1000);
    })
    }
    
    function Async2() {
      return new Promise((resolve)=>{
        setTimeout(() => {
          return resolve("我是异步方法二");
        }, 10); 
      })
    }
    
    async function test() {
      const v1 = await Async1();
      const v2 = await Async2();
      console.log(v1, v2);
    }
    
    test();

    可以看到直接输出resolve的参数 。

    (2)异步方法1不用await,异步方法2用await

    function Async1() {
    return new Promise((resolve)=>{
    setTimeout(() => {
        return resolve("我是异步方法1");
      }, 1000);
    })
    }
    
    function Async2() {
      return new Promise((resolve)=>{
        setTimeout(() => {
          return resolve("我是异步方法二");
        }, 10); 
      })
    }
    
    async function test() {
      const v1 = Async1();
      const v2 = await Async2();
      console.log(v1, v2);
    }
    
    test();

     可以看到没有await,直接返回一个pending,那么是否await来等待一个具体值呢?这个我们等下再考虑。

    (3)await是否可以等待其它函数,或者表达式:

    function normal(){
      let a=1;
    
      return"我是同步函数"
    }
    async function Async0(){
      return"我是异步函数零"
    }
    function Async1() {
    return new Promise((resolve)=>{
    setTimeout(() => {
        resolve("我是异步方法一")
      }, 1000);
    })
    }
    
    function Async2() {
      return new Promise((resolve)=>{
        setTimeout(() => {
          return resolve("我是异步方法二");
        }, 10); 
      })
    }
    
    async function test() {
      const num = await (1+1);
      const n = await normal()
      const v0 = await Async0();
      const v1 = await Async1();
      const v2 = await Async2();
      console.log(n,v0,v1, v2,num);
    }
    
    test();

     问题答案如上面演示,答案是可以的。

    (4)同步方法没有返回值时,await会怎么处理

    function normal(){
      let a=1;
    
    }
    
    function Async1() {
    return new Promise((resolve)=>{
    setTimeout(() => {
        resolve("我是异步方法一")
      }, 1000);
    })
    }
    
    function Async2() {
      return new Promise((resolve)=>{
        setTimeout(() => {
          return resolve("我是异步方法二");
        }, 10); 
      })
    }
    
    async function test() {
      const n = await normal()
      const v1 = await Async1();
      const v2 = await Async2();
      console.log(n,v1,v2)
    }

     当同步没有返回值时,await默认返回undefined,因为await会认为同步函数执行完了,然后await知道同步函数没有返回值。

    (5)异步方法没有返回值时,await怎么操作

    function normal(){
      let a=1;
    
    }
    
    function Async1() {
    return new Promise((resolve)=>{
    setTimeout(() => {
        resolve("我是异步方法一")
      }, 1000);
    })
    }
    
    function Async2() {
      return new Promise((resolve)=>{
        setTimeout(() => {
          // return resolve("我是异步方法二");把异步方法二返回值注释掉
        }, 10); 
      })
    }
    
    async function test() {
      const n = await normal()
      const v1 = await Async1();
      const v2 = await Async2();
      console.log(n,v1,v2)
    }
    
    test();

     此时await会直接退出,其实我觉得应该结果会直接阻塞才对,因为异步代码没有返回值,await就会一直等待,await一直等待,test函数就会奔溃,最后退出才对;因为await可以判断同步代码没有返回值,但是异步代码返回值什么时候返回,它是无法知道的。

    说明白一点,我们知道通常我们使用promise是用来包着一个异步操作的,等待异步操作完成,才把结果resolve出去,但是这个异步操作什么时候完成,没有人会知道,就像我们在像网络世界发起请求时,如果服务器响应速度快,我们就很快得到数据,相反,要是服务器很慢,我们就很晚才拿到数据,但是具体请求数据要多久才能拿到数据我们是不知道的,可能1ms,可能3s,亦或是1分钟,一天,十天,半个月,一年都有可能,只不过我们的日常应用都做了处理,如果超过一定时间没有请求得到数据,那么浏览器或者网页会返回404,或者其他通知用户请求数据失败的消息,以优化用户体验。

    回到上面那个例子,settimeout就是一个异步操作,里面resolve就是包含着我们的数据,如果我们一直不resolve,外面就一直拿不到数据,所以我自己觉得要是await在超出一段时间拿不到数据,它就认为请求失败,整个async函数全部退出,即使最前面的几个await拿到值了,它也会导致程序直接退出。这里说的不太准确,应该说阻塞才退,其实前面的await已经拿到值了,只不过后面的输出函数被阻塞了,输出不了。

    (6)声明了async的函数,有用await与不用await的区别:

    不用await:

    async function async1() {
      console.log( 'async1 start' )
       async2()
      console.log( 'async1 end' )
    }
    async function async2() {
      console.log( 'async2' )
    }
    async1()
    console.log( 'script start' )

    执行顺序与普通函数没什么区别:

     用了await:

    async function async1() {
      console.log( 'async1 start' )
      await async2()
      console.log( 'async1 end' )
    }
    async function async2() {
      console.log( 'async2' )
    }
    async1()
    console.log( 'script start' )

     所以对于await来说,等到的对象分2个情况

    • 不是promise对象
    • 是promise对象

    如果不是 promise , await会阻塞async函数内部的代码,先执行async外面的同步代码,同步代码执行完,再回到async内部,把这个非promise的东西,作为 await表达式的结果

    如果它等到的是一个 promise 对象,await 也会阻塞async内部后面的代码,先执行async外面的同步代码,等着 Promise 对象 fulfilled,然后把 resolve 的参数作为 await 表达式的运算结果。

    有的人可能会疑惑,为什么阻塞了,那个async2还是打印出来了,这其实是代码执行顺序是从由到左执行的,先执行了asyn2(),然后发现有await之后,再阻塞内部,然后跳到外部先执行。

    看到这里的同学可以去网上查查js的事件循环,然后查一下之前今日头条的那道面试题,不过我看到那个面试题的答案有两种,有个博主是写的很详细,而且解释起来也令我信服,但是执行结果和他给出的答案的有点出入,我也不知道怎么回事。感兴趣的可以看看:https://www.cnblogs.com/fundebug/p/10095355.html#4837719

    四、总结

    (1)async用来申明一个函数是等待异步函数,只有申明了async的函数,内部才可以使用await;

    (2)申明了async函数,那么返回值会是一个promise,返回的类型语法格式:promise{‘参数’},但是它仍属于同步方法。

    (3)await是用来等待一个表达式的,这个表达式可以是任何一个表达式,当返回的为非promise表达式时,无论await等到的是什么,等待异步函数最终会全部执行。当等到的是promise,而且这个promise没有返回值时,它就会导致等待异步函数阻塞,从await开始一直阻塞,然后退出,此时程序会输出await之前的执行结果。(就像promise的中文意思一样,承诺,就是它承诺了会返回一个数据,但是结果它没有返回,如此不诚信的行为将会导致整个函数异常)

    (4)当await内部阻塞时,会先去外面把其他同步函数执行完,再返回到当前阻塞代码继续执行。

     附上我画的声明了async的函数与promise函数与异步请求函数的关系:

     一个async可以包含多个promise,使用await达到多个promise.then()同步的效果:

     function fun1(){
      return new Promise((resolve)=>{
        setTimeout(() => {
          resolve('任务1')
        }, 500);
      })
    }
    
    function fun2(){
      return new Promise((resolve)=>{
        setTimeout(() => {
          resolve('任务2')
        }, 100);
      })
    }
    
    async function totalTask(){
      await fun1().then((response)=>{
        console.log(response)
      })
      await fun2().then((response)=>{
        console.log(response)
      })
    }
    totalTask()

     若没有async:

     function fun1(){
      return new Promise((resolve)=>{
        setTimeout(() => {
          resolve('任务1')
        }, 500);
      })
    }
    
    function fun2(){
      return new Promise((resolve)=>{
        setTimeout(() => {
          resolve('任务2')
        }, 100);
      })
    }
    
    function totalTask(){
    fun1().then((response)=>{
        console.log(response)
      })
    fun2().then((response)=>{
        console.log(response)
      })
    }
    totalTask()

     

    五、给出await与async在实际应用中的例子:

    1. async/await 的优势在于处理 then 链

    单一的 Promise 链并不能发现 async/await 的优势,但是,如果需要处理由多个 Promise 组成的 then 链的时候,优势就能体现出来了(很有意思,Promise 通过 then 链来解决多层回调的问题,现在又用 async/await 来进一步优化它)。

    假设一个业务,分多个步骤完成,每个步骤都是异步的,而且依赖于上一个步骤的结果。我们仍然用 setTimeout 来模拟异步操作

    /**
     * 传入参数 n,表示这个函数执行的时间(毫秒)
     * 执行的结果是 n + 200,这个值将用于下一步骤
     */
    function takeLongTime(n) {
        return new Promise(resolve => {
            setTimeout(() => resolve(n + 200), n);
        });
    }
    
    function step1(n) {
        console.log(`step1 with ${n}`);
        return takeLongTime(n);
    }
    
    function step2(n) {
        console.log(`step2 with ${n}`);
        return takeLongTime(n);
    }
    
    function step3(n) {
        console.log(`step3 with ${n}`);
        return takeLongTime(n);
    }

    现在用 Promise 方式来实现这三个步骤的处理

    function doIt() {
        console.time("doIt");
        const time1 = 300;
        step1(time1)
            .then(time2 => step2(time2))
            .then(time3 => step3(time3))
            .then(result => {
                console.log(`result is ${result}`);
                console.timeEnd("doIt");
            });
    }
    
    doIt();
    
    // c:var	est>node --harmony_async_await .
    // step1 with 300
    // step2 with 500
    // step3 with 700
    // result is 900
    // doIt: 1507.251ms

    输出结果 result 是 step3() 的参数 700 + 200 = 900doIt() 顺序执行了三个步骤,一共用了 300 + 500 + 700 = 1500 毫秒,和 console.time()/console.timeEnd() 计算的结果一致。

    如果用 async/await 来实现呢,会是这样:

    async function doIt() {
        console.time("doIt");
        const time1 = 300;
        const time2 = await step1(time1);
        const time3 = await step2(time2);
        const result = await step3(time3);
        console.log(`result is ${result}`);
        console.timeEnd("doIt");
    }
    
    doIt();

    结果和之前的 Promise 实现是一样的,但是这个代码看起来是不是清晰得多,几乎跟同步代码一样

    2. 还有更酷的

    现在把业务要求改一下,仍然是三个步骤,但每一个步骤都需要之前每个步骤的结果。

    function step1(n) {
        console.log(`step1 with ${n}`);
        return takeLongTime(n);
    }
    
    function step2(m, n) {
        console.log(`step2 with ${m} and ${n}`);
        return takeLongTime(m + n);
    }
    
    function step3(k, m, n) {
        console.log(`step3 with ${k}, ${m} and ${n}`);
        return takeLongTime(k + m + n);
    }

    这回先用 async/await 来写:

    async function doIt() {
        console.time("doIt");
        const time1 = 300;
        const time2 = await step1(time1);
        const time3 = await step2(time1, time2);
        const result = await step3(time1, time2, time3);
        console.log(`result is ${result}`);
        console.timeEnd("doIt");
    }
    
    doIt();
    
    // c:var	est>node --harmony_async_await .
    // step1 with 300
    // step2 with 800 = 300 + 500
    // step3 with 1800 = 300 + 500 + 1000
    // result is 2000
    // doIt: 2907.387ms

    除了觉得执行时间变长了之外,似乎和之前的示例没啥区别啊!别急,认真想想如果把它写成 Promise 方式实现会是什么样子?

    function doIt() {
        console.time("doIt");
        const time1 = 300;
        step1(time1)
            .then(time2 => {
                return step2(time1, time2)
                    .then(time3 => [time1, time2, time3]);
            })
            .then(times => {
                const [time1, time2, time3] = times;
                return step3(time1, time2, time3);
            })
            .then(result => {
                console.log(`result is ${result}`);
                console.timeEnd("doIt");
            });
    }
    
    doIt();

    参考文章:https://segmentfault.com/a/1190000007535316

    穷则独善其身,达则兼济天下……
  • 相关阅读:
    点滴积累【JS】---JS小功能(JS实现侧悬浮浮动)
    点滴积累【JS】---JS小功能(JS实现隐藏显示侧边栏,也就是分享栏的隐藏显示)
    点滴积累【JS】---JS小功能(JS实现排序)
    php修改排序,上移下移
    PHP获取上周、本周、上月、本月、本季度、上季度时间方法大全
    php简陋版实现微信公众号主动推送消息
    JQuery处理json与ajax返回JSON实例
    页面滚动动态加载数据,页面下拉自动加载内容 jquery
    CGI/FASTCGI/ISAPI区别
    CodeIgniter类库之Benchmarking Class ,计算代码的执行时间
  • 原文地址:https://www.cnblogs.com/hmy-666/p/14528296.html
Copyright © 2020-2023  润新知