async函数

334 阅读8分钟

一.含义

  • ES7标准引入了async函数,该函数专门用于处理异步操作,属于generator函数的语法糖
  • 看个例子
  // 1. generator函数
  function * gene(){
   yield 5;
   yield 'hello world'
  }
  var res=gene()
  console.log(res.next());//{value: 5, done: false}
  console.log(res.next());//{value: "hello world", done: false}
  console.log(res.next())//{value: undefined, done: true}
  
  // 2. async函数
  async function a(){
   await console.log(5);  //5 
   await console.log('hello world');  //hello world
  }
  // 返回的是一个promise对象(即使没有return)
  console.log('async返回值',a()) //async返回值 Promise {<pending>}

直观区别

  1. generator函数和async函数最直观的区别就是generator的*可以被替换成async并且换个位置,yield语句可以被替换成await语句
  2. generator函数需要通过next方法控制函数执行进度,而async函数不需要next等方法来进行控制
  • 官方的说法就是async函数自带执行器,所以和普通函数一样,直接调用函数就可以了

其他区别

  1. async返回的是一个promise对象,而generator函数返回的是一个iterator对象,该iterator对象需要调用next方法才能继续执行
  2. 官方说法是async函数比generator函数具有更好的普适性,因为generator函数的yield语句后面只能是thunk函数和promise对象,但是不理解
  • 关于thunk函数请看generator函数的异步应用
  • 而async函数后面可以跟promise对象和普通数据类型(普通数据类型会被转换为promise对象状态默认为resolved返回如果接的是普通函数,那么不会等待promise状态改变,也就是相当于没效果)

二.基本用法

  • async函数会返回一个promise对象,await语句后面可以接普通数据类型好promise对象
  • 注意return返回的promise对象可能在await语句执行完毕之前就返回了
  • 即使await语句卡住了,return也是可以返回promise对象!但是返回值为await返回的默认值undefined!说明value没有改变
 // 1. await语句后面接普通数据类型
 async function a(){
  // 1.1 普通数据类型有没有执行。此时看不出来
  await true;  // 执行了
  await 1;  // 执行了
  await 'h';  // 执行了
  // 1.2 使用()包裹运算语句
  var x;
  await (x=5+9);  // 执行了
  console.log('x',x) //x 14
  // 1.3 执行普通函数
  await console.log('await后面接函数');//await后面接函数
  return '有返回值'
 }
 // promise对象的[[PromiseValue]]为 "有返回值"
 console.log(a())
 
 // 2. async函数返回的是promise对象,所以可以作为await语句后面的语句
 async function timeout(ms) {
   await new Promise((resolve) => {
     setTimeout(resolve, ms); // 需要等待promise对象的状态变为resolved或者rejected才会继续执行
   });
 }
 
 async function asyncPrint(value, ms) {
   await timeout(ms);
   console.log(value);
 }
 // 因为没有return 所以打印的promise对象没有值
 console.log(asyncPrint('hello world', 1000));//Promise {<pending>}
 
 // 3. await函数后面接promise对象(状态不改变)
 async function q(){
  await new Promise((res,rej)=>{
   console.log("状态不改变")//状态不改变
   //res();// 注释掉res/rej,那么该函数一直停在这里
  }).then(()=>{console.log('res')},()=>{console.log('rej')})
  // 注释掉res/rej之后,这里不会打印了
  console.log('此处不会执行')
  return '即使await停止,还是会return返回promise对象,但是value未undefined默认值'
 }
 console.log(q());//[[PromiseValue]]: undefined

async函数的使用形式

 // 1. 函数声明(不能是匿名函数)
 // async function (){};//Function statements require a function name
 async function a(){}
 
 // 2.函数表达式
 var b=async function(){}
 
 // 3.作为对象属性
 var obj={async c(){}}
 console.log(obj)//{c: ƒ}
 
 // 4.类的方法
 class d{
  constructor(name){
   this.name=name
  }
  async getName(){
   await console.log("实例化类,执行async")
   // 4.1. return基本数据类型
   // return 'hello'
   // 4.2. promise对象
   // return new Promise((res,rej)=>{
   //  // 4.2.1 改变状态
   //  // res('4.2.1')
   //  // console.log('res')
   //  // 4.2.2 不改变
   //  console.log('4.2.2不改变')
   // })
   // 4.3 await 
   return await new Promise((res,rej)=>{
    //4.3.1
    // console.log("await改变promise状态")
    // res('改变成功')
    // 4.3.2 不改变
    console.log('3.2')
   })
  }
 }
 var dd=new d('dog')
 console.log(dd.getName())
 /*  4.
  1. [[PromiseValue]]: "hello"
  
  2.1 [[PromiseValue]]: "4.2.1"
  2.2 打印4.2.2不改变 得到[[PromiseValue]]:undefined
  
  3.1 [[PromiseValue]]: "改变成功"
  3.2 [[PromiseValue]]: undefined
  */
 // 所以return返回promise对象其实和return await promise对象的作用是一样的!

三.语法

promise对象

  • async函数会返回一个promise对象,所以我们可以对该对象添加then,catch等方法
  • 最重要的是,即使在async内部没有捕获到错误,也可以通过给返回的promise对象添加catch/reject方法来捕获错误
 // 1. async函数,用于捕获返回值
 async function a(){
  return 'hello world'
 }
 // 添加then方法(用于捕获返回值)
 a().then((data)=>{
  console.log(data);//hello world
 })
 
 // 2. async函数,用于捕获内部错误
 async function b(){
  throw new Error("err")
 }
 b().catch((err)=> {
  console.log(err) // Error: err
 })

promise对象状态变化

  • 当await后面接的不是promise对象时,如果是普通数据类型那么会直接执行,因为默认转换为promise对象且状态是resolved状态
  • 但是当为函数时,如果存在setTimeout,那么也不会等待,因为没有状态改变,所以立即返回。但是该函数依旧会执行
  • 如果是promise对象,那么必须等到状态改变为resolve/reject才能执行剩下的方法和return语句等,then/catch也无法执行
 // 1. 必须等待async函数的await执行完毕,才会执行返回的promise对象的then 
 var n=1
 async function a(){
  // 1.1 await后面接的不是promise对象,那么不需等待状态改变
  /* await setTimeout(function(){
   console.log('此时执行')
   n++;
  },1000) */
  /* 由于没有promise对象,不会等待状态改变,所以得到的n为1 */
  /* 但是setTimeout的确执行了,1秒后全局获取n会得到2 */
  
  // 1.2 await语句后面接的是promise对象,那么会等待状态改变
  await new Promise((res,rej)=>{
   setTimeout(function(){
    console.log('此时执行')
    n++;
    res()
   },1000)
  })
  /* 会在1秒后才return,所以得到的n:2 */
  return 'n的值为:'
 }
 // n的值为: 1
 a().then(data => console.log(data,n))

thenable对象

  • 如果await命令后面跟的是一个thenable对象(具有then方法的对象),那么await会把该对象当成promise处理
 // 1. 对象具有then方法,那么await会自动调用该方法
 var obj={
  then(res,rej){
   var s=new Date().getTime()
   setTimeout(function(){
    res(new Date().getTime()-s)
   },1000)
  }
 };  // 这个分号不能省。
 // 注意,适用立即执行函数,上个语句一定要加分号。。。
 (async () => {
   var res=await obj
   console.log(res);// 1001 
 })();
 
 // 2.还可以给类添加then方法,那么实例化对象也是thenable对象
 class Sleep {
   constructor(timeout) {
     this.timeout = timeout;
   }
   then(resolve, reject) {
     const startTime = Date.now();
     setTimeout(
       () => resolve(Date.now() - startTime),
       this.timeout
     );
   }
 };
 
 (async () => {
   const sleepTime = await new Sleep(1000);
   console.log(sleepTime); //1002
 })();
  • 根据async/await,可以实现整个程序的休眠
  /* 注意,只是让promise休眠了,主线程并没有休眠。。
   所以b().then()之后的下一行会立即执行!*/
 var s=new Date().getTime()
 async function b(){
  var d=await new Promise((res)=>{
   setTimeout(()=>{
    res('ddd')
   },1000)
  })
  console.log(d,new Date().getTime()-s)
 }
 b().then((res)=>{
  console.log('1000毫秒后执行')
 })
 console.log(new Date().getTime()-s)
 console.log(`休眠${new Date().getTime()-s}毫秒后才执行这里`)

错误处理

  1. then,catch可以接受reject,抛出的错误
 // 1. then的第二个参数接受reject数据
 async function f(){
  await new Promise((res,rej)=> rej("err"))
 }
 f().then(res=> {}, rej => {console.log(rej)});//err 
 
 // 2. catch可以接受reject的数据
 async function b(){
  await new Promise((res,rej)=>{
   rej('rej')
  })
 }
 b().catch(err => console.log(err));// rej 
 
 // 3. then的第二个参数函数可以接收抛出的错误
 async function c(){
  throw new Error("err")
 }
 c().then(res => console.log(res),
 rej => console.log(rej));//Error: err
 
 // 4. catch可以接收抛出的错误
 c().catch(err => console.log(err));//Error: err
  1. async函数的错误其实应该在内部捕获,使用try-catch语句
 async function e(){
  try{
   await new Promise((res,rej)=>{
    rej('err')
   })
  }catch(e){
   console.log('错误:',e);//错误: err
  }
 }
 e()

注意点

  1. 建议await语句都放在try-catch语句块中,因为await后面的promise对象可能抛出错误
  2. 多个await命令后面的异步操作,如果是独立的异步操作,最好同步触发(省时,一起触发了)
 function a(time,num){
  return new Promise((res)=>{
   setTimeout(()=>{
    res(num)
   },time)
  })
 }
 async function b(){
  // 1. Promise.all([]) 同步触发多个异步操作
  // var [x,y]= await Promise.all([a(1000,3),a(2000,5)])
  
  // 2. 分开写
  var x=a(1000,3)
  var y=a(2000,5)
  await x
  await y
  
  console.log(x,y);//3 5
 }
 b()
  1. await语句必须用在async函数中,用在其他地方会报错
  2. 数组的高阶方法(有参数)使用async可能会得到错误的结果。
 var arr=[1,2,3,4]
 function getdata(num,i){
  return new Promise((res,rej)=>{
   setTimeout(function(){
    res(num)
   },1000*i)
  })
 }
 // 1.forEach(并不是一个数执行完毕再到另一个数,而是差不多一起执行)
 // arr.forEach(async function(item,i){
 //  console.log(await getdata(item,Math.abs(2-i)))
 // })
 // 此时得到的结果为 3 2 4 1,因为异步操作设置的等待时间不同,所以得到顺序不同!
 // 所以不是我们想要的结果!
 
 // 2. map (此时和原来的一致,因为是按照顺序返回结果的)
 arr.map(async function(item,i){
  return await getdata(item,Math.abs(2-i))
 })
 // console.log(arr);//[1, 2, 3, 4]
 
 // 3. for循环(按照顺序执行,上个结束了才到下一个)
 async function b(){
  for(var i in arr){
   // 1 2 3 4
   console.log(await getdata(arr[i],Math.abs(2-i)))
  }
 }
 b()

本文使用 mdnice 排版