听说你不懂JavaScript的async/await

2,977 阅读6分钟

这周四,部门分享会。一位同事展示了运用node.js去制作爬虫,通过调用对方的接口爬取数据。由于如果通过node.js去大量的异步请求对方的数据,将会导致对方服务器崩溃,无法响应。因此同事通过async/await将代码改造成同步的方式,一个接着一个请求,顺序的去请求对方接口,得到数据。因此特地研究了一下async和await关键词。

什么是async和await

async

async用来修饰函数,用于将一个函数变成为异步函数,返回值是一个Promise。

被async修饰的函数有return的值

如果被async修饰的函数有return的值,那么return的值会成为Promise.resolve(value)的参数返回。可能不好理解,看下面的例子:

async function test(){
  return "有return的情况下的返回值"
}
//执行test(),返回了一个Promise
test().then((value)=>{
  console.log(value)  //此处Promise.resolve的参数,就是test函数return的值
})

控制台输出结果为: 有return的情况下的返回值

async修饰的函数没有return值

如果async修饰的函数没有return值,那么Promise.resolve(value)中的value返回的是undefined,看下面的例子:

async function test(){
}

test().then((value)=>{
  console.log(value)  //此处Promise.resolve的参数,由于test函数没有return值,所以是undefined
})

控制台输出结果为: undefined

await

await字面意思等待,它用来等待异步函数执行完毕。await会阻塞线程的执行,所以当使用await的时候,必须在被async修饰的异步函数中执行。

在一个普通函数的调用前面加await

在一个普通函数的调用前面加await,它会立马将普通函数的返回值返回回去,看下面的例子:

----------------------------有返回值-----------------------------------------
function sayHi(){
  return "hi,world";
}

async function test(){
  const hi=await sayHi();
  console.log(hi);
}

test()
//控制台输出:hi,world

----------------------------没有返回值-----------------------------------------
function sayHi(){
  var hi= "hi,world";
}

async function test(){
  const hi=await sayHi();
  console.log(hi);
}

test()
//控制台输出:undefined

我们可以看到,在async函数中,在普通函数的调用前面加await,会将普通函数的返回值返回回去,如果该普通函数执行后没有返回值,则返回undefined。

在被async修饰的函数执行前加await

在被async修饰的函数执行前加await,那么会等待async返回的Promise执行完毕,如果Promise执行成功,状态从Pending=》resolved,那么就会返回Promise.resolve(value)中的value值,即async 函数中的return值。看下面的例子:

async function sayHi(){
  return "hi,world";
}

async function test(){
  const hi=await sayHi();
  console.log(hi);
}

test()

控制台输出结果:hi,world

在Promise对象前使用await

在Promise对象前使用await,我们都知道async返回的是一个Promise对象,那我们为什么还要再来讨论await修饰Promise对象呢?

那是因为async返回的Promise对象我们并没有指定它的then()方法,而再次来讨论Promise对象前面添加await修饰符主要是想帮助大家更深刻的理解:await修饰Promise对象,它的返回值是什么?看一下下面的例子,我们猜测一下控制台输出的结果。

async function test(){
  const hi=await new Promise(function(resolve,reject){
    resolve("hi,world")
  }).then((value)=>{
    return "hello,world"
  });
  console.log(hi);
}

test()

上面这段代码在控制台中输出的结果是:hello,world

WTF!!?你不是说返回的是Promise.resolve(value)中的value值吗?

客官冷静,冷静。

由于async函数只是返回一个Promise对象,并没有指定Promise.then()方法,而用await修饰async的调用,await帮助我们添加了Promise.then(),而且将Promise的状态从Pending=》resolved,下面hi等价于myHi

 async function sayHi(){
  return "hi,world";
}

async function test(){
  const hi=await sayHi();

  const myHi=await new Promise((resolve,reject)=>{
    resolve("hi,world")
  }).then((value)=>{
    return value;
  })

  console.log(hi); //输出:hi,world
  console.log(myHi)  //输出:hi,world
}

test()

所以在说await修饰的async函数时,我说返回的值是是Promise.resolve(value)中的value值。

如果此时async修饰的函数没有返回值,那么此时的value值就是undefined,所以await修饰的返回值也是undefined。关于这一点,我就不再展示了,大家可以自己尝试一下。

再看下面的代码:

async function sayHi(){
  return "hi,world";
}

async function test(){
  const hi=await sayHi();

  const myHi=await new Promise((resolve,reject)=>{
    resolve("hi,world")
  }).then((value)=>{
    return "hello,world";   //修改成return "hello,world"
  })

  console.log(hi);  //输出:hi,world
  console.log(myHi)  //输出:hello,world
}

test()

此时我们在Promise.then()中指定Promise.resolve(value)返回的不再是value,而是我们指定的内容hello,world,那么此时await的值就是hello,world

如果当Promise有多个Promise.then()方法的时候,返回最后一个Promise.then()的返回值。如果没有返回值,则返回undefined。看下面这段代码:

async function sayHi(){
  return "hi,world";
}

async function test(){
  const hi=await sayHi();

  const myHi=await new Promise((resolve,reject)=>{
    resolve("hi,world")
  }).then((value)=>{
    return "hello,world";
  }).then((value)=>{
    return "hi,大麦"
  })

  console.log(hi);  //输出:hi,world
  console.log(myHi) //输出:hi,大麦
}

test()

如果我们的Promise状态是从pending=》rejected,那此时await的返回值是什么呢?

async function sayHi(){
  return "hi,world";
}

async function test(){
  const hi=await sayHi();

  const myHi=await new Promise((resolve,reject)=>{
    reject("error")
  }).then((value)=>{
    return "hello,world";
  },(error)=>{
    return value
  })

  console.log(hi);
  console.log(myHi)  //输出:error
}

test()

我们可以看到,此时类返回值是Promise.reject(value)中的返回值,如果没有返回值,则此时的await返回值是undefined

不过我们经常在使用Promise对象的时候不会去指定Proise.reject的回调函数。例如:

async function sayHi(){
  return "hi,world";
}

async function test(){
  const hi=await sayHi();

  const myHi=await new Promise((resolve,reject)=>{
    reject("error")
  }).then((value)=>{
    return "hello,world";
  })    //没有指定Promise.reject的回调函数

  console.log(hi);
  console.log(myHi)
}

test()

此时程序就会出错,报uncaught exception: error的错误。所以我们为了避免这种情况的发生,我们应该把await修饰的Promise放到try{}.catch()中。例如:

async function sayHi(){
  return "hi,world";
}

async function test(){
  const hi=await sayHi();

  try{
    var myHi=await new Promise((resolve,reject)=>{     //把const修改成var关键词
      reject("error")
    }).then((value)=>{
      return "hello,world";
    })
  }catch(e) {
    myHi=e;
  }

  console.log(hi);
  console.log(myHi)  //输出:error
}

test()

async和await大显身手

当Promise需要多层调用Promise.then()的时候,就能体现async和await的优点了。

function add(num){
  return num+200;
}

async function test(){

  const step1=await add(100);
  const step2=await add(step1);
  const step3=await add(step2);
  console.log(step3)
}

function thenTest(){
  const result=new Promise( (resolve,reject)=> {
    resolve(100)
  }).then((value)=>{
    return add(value)
  }).then((value)=>{
    return add(value)
  }).then((value)=>{
    console.log(add(value))
  })

}

test()  //输出:700   (100+200+200+200)
thenTest()  //输出:700   (100+200+200+200)

我们可以看到async和await可以避免Promise的多次then()函数的调用,让语法更加清晰。

我是大麦,如果喜欢我的文章,请给我一颗小心心。