zoukankan      html  css  js  c++  java
  • Async/Await

    Async/Await

      async/await是写异步代码的新方式,以前的方法有回调函数和Promise。
      async/await是基于Promise实现的,它不能用于普通的回调函数。
      async/await与Promise一样,是非阻塞的。
      async/await使得异步代码看起来像同步代码,这正是它的魔力所在。

      是es7中提出来的异步解决方法,是目前解决异步编程终极解决方案,以promise为基础,其实也就是generator的高级语法糖,本身自己就相当于一个迭代生成器(状态机),它并不需要手动通过  next()来调用自己,与普通函数一样。async就相当于generator函数中的*,await相当于yield,

    async 函数是什么

    一句话,async 函数就是 Generator 函数的语法糖。()

    async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成。

    async 作为一个关键字放到函数前面,用于表示函数是一个异步函数,因为async就是异步的意思, 异步函数也就意味着该函数的执行不会阻塞后面代码的执行。举个例子:

    // async函数返回的是一个Promise对象,async函数(包括函数语句、函数表达式、Lambda表达式)会返回一个Promise对象,如果在函数中return一个直接量,async会把这个直接量通过Promise.resolve() 封装成 Promise 对象
    
    async function timeout( ) {
        return setTimeout(() => console.log(666),1000)
    }
    timeout();
    console.log('虽然在后面,但是我先执行');
    

    async 函数返回的是一个 Promise 对象。async 函数(包含函数语句、函数表达式、Lambda表达式)会返回一个 Promise 对象,如果在函数中 return 一个直接量,async 会把这个直接量通过 Promise.resolve() 封装成 Promise 对象。

    async 函数返回的是一个 Promise 对象,所以在最外层不能用 await 获取其返回值的情况下,我们当然应该用原来的方式:then() 链来处理这个 Promise 对象,就像这样:

    async function testAsync( ) {
      return 'hello async';
    }
    
    testAsync().then((v) => {
      console.log(v);    // 输出 "hello async"
    });
    

    现在回过头来想下,如果 async 函数没有返回值,又该如何?很容易想到,它会返回 Promise.resolve(undefined)。

    await

    await 表达式会 暂停 当前 async function 的执行,等待 Promise 处理完成。若 Promise 正常处理(fulfilled),其回调的resolve函数参数作为 await 表达式的值,继续执行 async function。

    若 Promise 处理异常(rejected),await 表达式会把 Promise 的异常原因抛出。

    另外,如果 await 操作符后的表达式的值不是一个 Promise,则返回该值 本身

    function getSomething( ) {
      return 'something';
    }
    
    async function testAsync( ) {
      return Promise.resolve('hello async');
    }
    
    async function test( ) {
      const v1 = await getSomething();
      const v2 = await testAsync();
      console.log(v1, v2);
    }
    
    test();
    

    记住,await 关键字只在异步函数内有效。如果你在异步函数外使用它,会抛出语法错误。 注意,当异步函数暂停时,它调用的函数会继续执行(收到异步函数返回的隐式Promise)。

    async/await 帮我们干了啥

    上面已经说明了 async 会将其后的函数(函数表达式或 Lambda)的返回值封装成一个 Promise 对象,而 await 会等待这个 Promise 完成,并将其 resolve 的结果返回出来。

    现在举例,用 setTimeout 模拟耗时的异步操作,先来看看不用 async/await 会怎么写:

    function takeLongTime( ) {
      return new Promise(resolve => {
        setTimeout(() => resolve('long_time_value'), 1000);
      });
    }
    
    takeLongTime().then((v) => {
        console.log('got', v);
    });
    

    如果改用 async/await 呢,会是这样:

    function takeLongTime( ) {
      return new Promise(resolve => {
        setTimeout(() => resolve('long_time_value'), 1000);
      });
    }
    
    takeLongTime().then((v) => {
        const v = await takeLongTime();
        console.log(v);
    });
    

    优势在于处理 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();
    
    // step1 with 300
    // step2 with 500
    // step3 with 700
    // result is 900
    // doIt: 1503.638916015625ms
    

    如果用 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 实现是一样的,但是这个代码看起来是不是清晰得多,几乎跟同步代码一样。

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

    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();
    
    // step1 with 300
    // step2 with 800 = 300 + 500
    // step3 with 1800 = 300 + 500 + 1000
    // result is 2000
    // doIt: 2903.52001953125ms
    

    除了觉得执行时间变长了之外,似乎和之前的示例没啥区别啊!别急,认真想想如果把它写成 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();
    

    有没有感觉有点复杂的样子?那一堆参数处理,就是 Promise 方案的死穴—— 参数传递太麻烦了,看着就晕!

    注意点

    大多数人的误区

    async function async1( ){
      console.log('async1 start');
      await async2();
      console.log('async1 end');
    }
    async function async2( ){
      console.log('async2');
    }
    async1();
    console.log('i am koala');
    

    我想会有一些开发者认为await是把同步变为异步,执行顺序是这样

    "async1 start"
    "async2"
    "async1 end"
    "i am koala"
    

    然而并不是,正确的执行顺序是

    "async1 start"
    "async2"
    "i am koala"
    "async1 end"
    

    解释一下原因:

    “ async 函数返回一个 Promise 对象,当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句。” ——阮一峰ES6

    看不懂打印顺序的话,可以看 这个博客

    运行结果是 rejected

    await 命令后面的 Promise 对象,运行结果可能是 rejected,所以最好把 await 命令放在 try...catch 代码块中。

    /** * await处理catch异常情况 * @param {Promise} promise promise对象 * @returns {Array} */
    function awaitWrap(promise) {
      if (!promise || !Promise.prototype.isPrototypeOf(promise)) {
        return new Promise((resolve, reject) => {
          reject(new Error('requires promises as the param'));
        }).catch((err) => {
          return [null, err];
        });
      }
      return promise
        .then(function( ) {
          return [...arguments, null];
        }).catch(err => {
          return [null, err];
        });
    }
    
    async function testAsync( ) {
      return Promise.resolve('hello async');
    }
    
    async function test( ) {
      const [res, err] = await awaitWrap(testAsync());
    
      console.log(res, err);
    }
    
    test();
    

    async 函数是非常新的语法功能,新到都不属于 ES6,而是属于 ES7。目前,它仍处于提案阶段,但是转码器 Babel 和 regenerator 都已经支持,转码后就能使用。

    协程

    协程,又称微线程,纤程。英文名Coroutine。 协程的概念很早就提出来了,但直到最近几年才在某些语言(如Lua)中得到广泛应用。

    子程序,或者称为函数,在所有语言中都是层级调用,比如A调用B,B在执行过程中又调用了C,C执行完毕返回,B执行完毕返回,最后是A执行完毕。

    所以子程序调用是通过栈实现的,一个线程就是执行一个子程序。

    子程序调用总是一个入口,一次返回,调用顺序是明确的。而协程的调用和子程序不同。

    协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。

    注意,在一个子程序中中断,去执行其他子程序,不是函数调用,有点类似CPU的中断。

    • 进程>线程>协程
    • 协程的第一大优势是具有极高的执行效率,因为子程序切换不是线程切换,而是由程序自身控制,因此没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显;
    • 协程的第二大优势是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多;
    • 协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行,需要注意的是:在一个子程序中中断,去执行其他子程序,这并不是函数调用,有点类似于CPU的中断;
    • 用汽车和公路举个例子:js公路只是单行道(主线程),但是有很多车道(辅助线程)都可以汇入车流(异步任务完成后回调进入主线程的任务队列);generator把js公路变成了多车道(协程实现),但是同一时间只有一个车道上的车能开(依然单线程),不过可以自由变道(移交控制权);
    • 协程意思是多个线程互相协作,完成异步任务,运行流程大致如下:
      • 1)协程A开始执行;
      • 2)协程A执行到一半,进入暂停,执行权转移到协程B;
      • 3)一段时间后,协程B交还执行权;
      • 4)协程A恢复执行;
    • 协程是一个无优先级的子程序调度组件,允许子程序在特定的地点挂起恢复;
    • 线程包含于进程,协程包含于线程,只要内存足够,一个线程中可以有任意多个协程,但某一个时刻只能有一个协程在运行,多个协程分享该线程分配到的计算机资源;
    • 就实际使用理解来说,协程允许我们写同步代码的逻辑,却做着异步的事,避免了回调嵌套,使得代码逻辑清晰;
    • 何时挂起,唤醒协程:协程是为了使用异步的优势,异步操作是为了避免IO操作阻塞线程,那么协程挂起的时刻应该是当前协程发起异步操作的时候,而唤醒应该在其他协程退出,并且他的异步操作完成时;
    • 单线程内开启协程,一旦遇到io,从应用程序级别(而非操作系统)控制切换对比操作系统控制线程的切换,用户在单线程内控制协程的切换,优点如下:
      • 1)协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级;
      • 2)单线程内就可以实现并发的效果,最大限度地利用cpu;
  • 相关阅读:
    spring学习(二十九)--参数绑定注解
    spring学习(二十八)--@Async线程池
    spring学习(二十七)--@Async
    spring学习(二十六)--@ContextConfiguration、@RunWith(SpringJUnit4ClassRunner.class)、@ComponentScan
    ## ansible 批量在远程主机上执行命令 ##
    Celery
    Flask-Script
    Flask-SQLAlchemy
    SQLAlchemy
    数据库连接池
  • 原文地址:https://www.cnblogs.com/xuzhenlei/p/12410319.html
Copyright © 2011-2022 走看看