async 函数

ES2017 标准引入了 async 函数,使得异步操作变得更加方便。

async 函数是什么?一句话,它就是 Generator 函数的语法糖。

async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。

但是,async函数对 Generator 函数的改进,体现在以下四点。

(1)内置执行器。

Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。

asyncReadFile();

上面的代码调用了asyncReadFile函数,然后它就会自动执行,输出最后结果。这完全不像 Generator 函数,需要调用next方法,或者用co模块,才能真正执行,得到最后结果。

(2)更好的语义。

asyncawait,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。

(3)更广的适用性。

co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。

(4)返回值是 Promise。

async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。

进一步说,async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖。

Promise 对象的状态变化

async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。

其实,使用async函数是为了可以将 async函数内容的所有异步操作完成后才返回结果,而Generator函数是可以一步一步的保存状态,如果一次都把 next 调用,那么是不会等待异步操作的完成,会先返回结果。

function* getHello(ms) {
  console.log('开始');
  yield new Promise((resolve) => {
    setTimeout(() => {
      console.log('Hello');
      resolve(12)
    }, ms)
  });
  console.log('中间');
}
var f = getHello(2000);
var result = f.next();
result.value.then((data) => {
  console.log(data);
})
console.log('3');

输出

开始 
3
Hello // 2s后
12 // 2s后

上面代码可以看出,调用了 next 方法后返回的 Promise 对象仍然是处于 pending 状态,而不是等待其异步操作完成后才会返回结果。

async function getHello(ms) {
  console.log('开始');
  await new Promise((resolve) => {
    console.log('Hello');
    setTimeout(() => {
      console.log('world');
      resolve(12)
    }, ms)
  });
  console.log('中间');
}
getHello(2000)
console.log('3');

输出

开始
hello
3
world // 2s后
中间 // 2s后

上面代码中,await 会是让程序处于等待状态,等异步操作完成返回结果后,才会执行后面的代码

不过,实际上,它们还是一样的,最后都是可以在函数外面处理异步操作完成后的结果。

await 命令

正常情况下,await命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。

async function f() {
  // 等同于
  // return 123;
  return await 123;
}

f().then(v => console.log(v))
// 123

使用注意点

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

async function myFunction() {
  try {
    await somethingThatReturnsAPromise();
  } catch (err) {
    console.log(err);
  }
}

// 另一种写法

async function myFunction() {
  await somethingThatReturnsAPromise()
  .catch(function (err) {
    console.log(err);
  });
}

第二点,多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。

let foo = await getFoo();
let bar = await getBar();

上面代码中,getFoogetBar是两个独立的异步操作(即互不依赖),被写成继发关系。这样比较耗时,因为只有getFoo完成以后,才会执行getBar,完全可以让它们同时触发。

// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);

// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;

上面两种写法,getFoogetBar都是同时触发,这样就会缩短程序的执行时间。

第三点,await命令只能用在async函数之中,如果用在普通函数,就会报错。

async function dbFuc(db) {
  let docs = [{}, {}, {}];

  // 报错
  docs.forEach(function (doc) {
    await db.post(doc);
  });
}

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')

这组代码的输出结果为

//async1 start
//async2
//script start
//async1 end

再看另外一组代码

async function async1() {
  console.log('async1 start')
  await new Promise((resolve)=> {
    setTimeout(()=> {
      resolve();
    }, 1000)
  }).then(()=>{
    console.log("resolve")
  })
  console.log('async1 end')
}

async1()
console.log('script start')

这一组代码的输出结果为

//async1 start
//script start
//resolve
//async1 end

从上面代码可以看出来,await 命令后面的代码相当于then 方法,会被 await 命令阻塞,只有当async外的同步代码执行完毕,才再回到async内部等promise状态达到fulfill的时候(如果有 promise)再继续执行下面的代码。

const { resolve } = require("path");

async function async1() {
  console.log("async1 start");
  await async2();
  console.log("async1 end");
}

async function async2() {
  console.log("async2");
}

console.log("script star");

setTimeout(() => {
  console.log("setTimeout");
}, 0);

async1();

new Promise((resolve) => {
  console.log("promise1");
  resolve();
}).then(() => {
  console.log("promise2");
});

console.log("script end");

结果为

//script star
//async1 start
//async2      
//promise1    
//script end  
//async1 end  
//promise2    
//setTimeou
 posted on 2021-06-06 18:44  kly99  阅读(159)  评论(0)    收藏  举报