async 和 await 是 js 中的关键字,从字面意思上来看, async 是"异步", await 是"是等待异步", async 可以单独使用,用来声明一个异步函数,返回一个 Promise 对象,而 await 必须在 async 函数中使用

使用async

先来看看普通的函数

function test1() {
  return 'hello async'
}
let a = test1()
console.log(b)

// 打印结果: 'hello async'

使用了async的函数

async function test2() {
      return 'hello async'
}
let b = test2()
console.log(b)

// 打印结果: Promise {<fulfilled>: "hello async"}

由此可见,使用了 async 关键字的 test2 函数返回的是一个 Promise 对象,所以可以使用 then 的方式来异步执行获取结果,结果同 test1

test2().then(res => {
  console.log(res)
})

// 打印结果:'hello async'

再来看看await

使用:

function testAsync() {
  return Promise.resolve("hello await")
}

async function testAwait() {
  const a = await testAsync()
  console.log(a)
}

testAwait()

// 打印结果:'hello await'

使用 await 关键字只需注意一点即await 必须在 async 函数内使用

await的等待

首先承认 await 是一个运算符,一般来说使用 await 后面会等待一个 Promise 对象,但也有可能是一个普通的值,那么就有两种情况

  1. 当等待的是一个普通的值,那么 await 表达式运算的结果就是它等到的东西
  2. 当等待的是一个 Promise 对象,那 await 就忙了起来,它会阻塞后面的代码,等着 Promise resolve ,然后拿到 resolve 的值,作为 await 表达式的结果,这里的阻塞只会在当前 async 函数内阻塞,不会影响全局线程,这也是为什么 await 必须定义在 async 函数内

那么什么时候会用到 async / await

当同步的代码需要使用异步的代码获取的值
比如下面这样:

// 模拟异步的test函数 结果是10
function test() {
  return new Promise(resolve => {
    setTimeout(() => resolve(10), 1000);
  });
}
// 函数fn
function fn() {
  // 定义一个变量num
  var num;
  test().then(res => {
    // 将test函数结果赋值给num
    num = res
  })
  // 打印num
  console.log(num)  // undefined
}
// 调用函数fn
fn()

上面的代码可以看到打印 num 是 undefined,这是因为 test 函数是异步的,需要1秒后才能获取到值,并赋值给 num,但是我整体代码是同步的,可不会等你,所以 num 还未赋值,打印 num 出来当然是 undefined了

接下来使用 await,修改代码如下:

// 函数fn 使用 async / await
async function fn() {
  // 定义一个变量num
  var num;
  await test().then(res => {
    // 将test函数结果赋值给num
    num = res
  })
  // 打印num
  console.log(num)  // 10
}
// 调用函数fn
fn()

成功了!这次打印 num 的结果是 10,分析下:当运行到 test 函数,await 发现它等到的是一个异步函数,于是它让"大家伙"把活先停停(阻塞),先等这个家伙干完我们再接着干(等到 test 异步函数的值),终于等到异步函数的值后,await 宣布大家继续干活,接下来就是将值赋给 num,然后打印 num,结果是 10,这就是上面例子的执行流程

async/await 的优势在于处理 then 链
单一的 Promise 链并不能体现出 async/await 的优势,但当需要处理由多个 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\test>node --harmony_async_await .
// step1 with 300
// step2 with 500
// step3 with 700
// result is 900
// doIt: 1507.251ms

输出结果 result 是 step3() 的参数 700 + 200 = 900。doIt() 顺序执行了三个步骤,一共用了 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 实现是一样的,但是这个代码看起来是不是清晰得多,几乎跟同步代码一样

以上就是我对 async/await 的理解,有不对的地方欢迎指出,求知若饥,虚心若愚!

Last modification:August 12th, 2021 at 07:35 pm
如果觉得我的文章对你有用,请随意赞赏