好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

ES6+ async/await

ES6+ async/await

1. 前言

前面几节我们已经学习了 解决 异步的方案 Promise 和 Generator,上一节我们也通过 一个 案例使用 Promise + Generator 实现了 一个 比较好的异步 解决方 案。同时我们实现了 一个 简版的 co 库,让我们在使用 Generator 函数 处理异步任务时更加方便,但这不是 最完 美的 解决方 案。

本节我们将学习 ES7 推出的 async/await 其特性是对 JS 的异步编程进行了重要的改进,在不阻塞主线程的情况下,它给我们提供了使用同步 代码 的风格来编写异步任务的能力。另外,我们要明确的是 async/await 其实是 Promise + Generator 的语法糖,为了帮助我们像写同步 代码 一样书写异步 代码 , 代码 风格更优雅, 错误 捕获也更容易。

本节我们将通过对上一节案例的改造。在不需要 co 库的情况下直接使用 async/await 让我们更加深刻地理解异步方案的演变过程。

2. 改造上节案例

上一节 我们通过 一个 案例来讲解 Promise + Generator 在实际应用中的使用,通过 Generator 函数 和 yield 让异步 代码 看起来像同步 代码 一样执行。但是这样里面存在的 一个 问题就是 生成 器 函数 直接执行,需要手动处理。为了 解决 深层回调的问题我们借助了 co 库来帮助我们去执行 生成 器 函数 ,从而 解决 了回调地狱的问题。下面是上一节的 代码 。

  const   ajax   =   function  ( api )   { 
   return   new   Promise  (  ( resolve ,  reject )   =>   { 
     setTimeout  (  (  )   =>   { 
       if   ( api  ===   'api_1'  )   { 
         resolve  (  'api_2'  )  ; 
       } 
       if   ( api  ===   'api_2'  )   { 
         resolve  (  )  ; 
       } 
     }  ,   ) 
   }  ) 
 } 

 function   *   getValue  (  )   { 
   const  api  =   yield   ajax  (  'api_1'  )  ; 
   const  value  =   yield   ajax  ( api )  ; 
   return  value ; 
 } 

 co  (  getValue  (  )  )  .  then  ( res  =>   { 
  console .  log  ( res )  ; 
 }  ) 
 

上面的 代码 中 getValue 是 生成 器 函数 ,不能直接 调用 ,这里用 co 库来进行执行,然后通过 Promise 的链式 调用 获取 执行后的结果。但是这里借助了 co 的库,我们其实最希望的是能像执行普通 函数 一样直接 调用 getValue 就能执行并得到结果。 async/await 的出现就是为了抹平在 调用 时所做的额外步骤。那让我们看看 async/await 是怎么用的:

  async   function   getValue  (  )   { 
   const  api  =   await   ajax  (  'api_1'  )  ; 
   const  value  =   await   ajax  ( api )  ; 
  console .  log  ( value ) 
   return  value ; 
 } 
 getValue  (  ) 	 // 控制台打印 value的值是:100 
 

上面的 代码 中我们可以看出使用 async/await 定义的 getValue 函数 和 生成 器 函数 */yield 定义的基本相同,但是在执行时 async/await 定义的 函数 直接 调用 即可。从这里我们就能看到 async/await 的优点,无需过多的操作非常优雅和简洁。

3. 用法

上面我们基本了解了 async 函数 ,下面我们就来看看它的基本使用和需要注意的地方。

定义 一个 异步 函数 时需要使用 async 和 function 关键字一起来完成,类似 生成 器 函数 中的 yield 来暂停异步任务,在 async 函数 中使用 await 关键去等待异步任务返回的结果。

async 函数 其本质是 Promise + Generator 函数 组成的语法糖,它为了减少了 Promise 的链式 调用 ,解放了 Generator 函数 的单步执行。主要语法如下:

  async   function   name  (  [ p ara m [  ,  p ara m [  ,   ...  p ara m ]  ]  ]  )   {  
    statements 
 } 
 

上面 代码 中的 statements 是 函数 主体的表达式,async 函数 可以通过 return 来返回 一个 值,这个返回值会被包装成 一个 Promise 实例,可以被链式 调用 。下面我们来看两段等价 代码 。

  // 下面两段 代码 时相同的 
 async   function   foo  (  )   { 
    return  
 } 
 function   foo  (  )   { 
    return  Promise .  resolve  (  ) 
 } 

 // 下面两段 代码 时相同的 
 async   function   foo  (  )   { 
   await   ; 
 } 
 function   foo  (  )   { 
    return  Promise .  resolve  (  )  .  then  (  (  )   =>  undefined ) 
 } 
 

上面的两段 代码 效果 时相同的,这里我们就不去探究 async 函数 是怎么实现的,其大概原理类似上节写的 co 库,有兴趣的小伙伴可以去 @L_ 301 _1@ 上去看看 async 函数 编译是什么样子的。

当在 async 函数 中返回的是 一个 普通值或 await 后跟 一个 普通值时,此时的 async 函数 是同步的。在 Promise 中失败是不能被 try...catch 捕获的,需要通过 catch 的方式来捕获 错误 。而使用 async 函数 则是可以通过 try...catch 来捕获。

  async   function   foo  (  )   { 
	 return   new   Error  (  'Throw an error'  )  ; 
 } 

 foo  (  )  .  then  ( res  =>   { 
  console .  log  ( res ) 
 }  )  .  catch  ( err  =>   { 
  console .  error  ( err ) 	 // Error: Throw an error 
 }  ) 

 async   function   foo2  (  )   { 
   try  { 
     var  v  =   await   foo  (  ) 
    console .  log  ( v ) 
   }   catch  ( e )   { 
    console .  log  ( e )  ; 	 // Error: Throw an error 
   } 
 } 
 foo2  (  ) 
 

上面的 代码 中在执行 foo() 直接抛出了 一个 错误 ,而 Promise 和 async/await 对 错误 的捕获是不同的,我们知道 Promise 是通过 then 中的失败回调和 catch 来捕获 错误 的,而 async 函数 使用的是 try...catch 更像同步的方式。

3.1 错误 捕获

但是有个问题,当程序需要同时处理多个异步任务时,那我们使用 async/await 怎样捕获那个异步任务出现 错误 呢?try 块中的 代码 只要程序出现 错误 就会抛出 错误 ,但是不知道是哪个异步任务出错了不利于定位问题。如果使用多个 try...catch :

  const   task   =   function   ( num )   { 
   return   new   Promise  (  ( resolve ,  reject )   =>   { 
     setTimeout  (  (  )   =>   { 
       if   ( num  ===   )   { 
         reject  (  'throw error'  ) 
       }   else   { 
       	 resolve  (  'imooc'  )  ;  
       } 
     }  ,   ) 
   }  ) 
 } 

 async   function   foo  (  )   { 
   try   { 
     let  res1  =   await   task  (  )  ; 
     try   { 
       let  res2  =   await   task  (  )  ; 
       try   { 
         let  res3  =   await   task  (  )  ; 
       }   catch  ( e )   { 
        console .  log  (  'res3'  ,  e ) 
       } 
     }   catch  ( e )   { 
      console .  log  (  'res2'  ,  e ) 
     } 
   }   catch  ( e )   { 
    console .  log  (  'res1'  ,  e ) 
   } 
 } 
 foo  (  ) 	 // res3 throw error 
 

看到上面的 代码 你是不是觉得很难受啊,又回到了嵌套地狱的原始问题了。async 函数 在异常捕获时,没有非常完美的 解决方 案,这主要源自依赖 try...catch 对 错误 的捕获。但有一些还算比较优雅的 解决方 案,我们已经知道了 async 函数 返回的是 一个 Promise 那么我们是不是可以使用 Promise 的 catch 来捕获呢?答案是当然的呢。

  async   function   foo  (  )   { 
   let  res1  =   await   task  (  )  .  catch  ( err  =>  console .  log  (  'res1'  ,  err )  )  ; 
   let  res2  =   await   task  (  )  .  catch  ( err  =>  console .  log  (  'res2'  ,  err )  )  ; 
   let  res3  =   await   task  (  )  .  catch  ( err  =>  console .  log  (  'res3'  ,  err )  )  ; 
 } 
 foo  (  ) 	 // res3 throw error 
 

上面的 代码 看起来就比嵌套的 try...catch 感觉好很多,这也是 一个 比较好的 解决方 式。在使用 catch 时需要弄清楚 Promise 和 async 函数 之 间的 关系,不然就很难理解这种写法。

3.2 滥用 async/await

既然 async/await 这么优雅简洁,那在编程的过程中都使用这个就好啦!其实这里是 一个 坑,很多时候 async/await 都会被滥用导致程序卡顿,执行时间过长。

  async   function   foo  (  )   { 
   let  res1  =   await   task  (  )  ; 
   let  res2  =   await   task  (  )  ; 
   let  res3  =   await   task  (  )  ; 

   return   {  res1 ,  res2 ,  res3  } 
 } 
 foo  (  ) 
 

在很多时候我们会写成这样的 代码 ,如果后 一个 任务依赖前 一个 任务这样写完全没问题,但是如果是三个独立的异步任务,那这样写就会导致程序执行时间加长。这样的 代码 过于同步化,我们需要牢记的是 await 看起来是同步的,但它仍然属于异步的 内容 ,最终还是走的回调,只是语言底层给我们做了很多工作。

针对没有关联的异步任务我们需要把它们解开,

  const   task   =   function   ( num )   { 
   return   new   Promise  (  ( resolve ,  reject )   =>   { 
     setTimeout  (  (  )   =>   { 
       	 resolve  (  'imooc '   +  num )  ;  
     }  ,   ) 
   }  ) 
 } 

 async   function   foo  (  )   { 
   let  res1Promes  =   task  (  )  ; 
   let  res2Promes  =   task  (  )  ; 
   let  res3Promes  =   task  (  )  ; 

   let  res1  =   await  res1Promes ; 
   let  res2  =   await  res2Promes ; 
   let  res3  =   await  res3Promes ; 

	console .  log  (  {  res1 ,  res2 ,  res3  }  ) 

   return   {  res1 ,  res2 ,  res3  } 
 } 
 foo  (  )  ; 	 // { res1: 'imooc 100', res2: 'imooc 200', res3: 'imooc 300' } 
 

这里需要明白的一点是:为什么要把 task 拿到 await 外面去执行呢?await 的本质就是暂停异步任务,等待返回结果,等到结果返回后就会继续往下执行。还要知道的是每个 task 都是 一个 异步任务,像之前的那种写法,await 会等待上 一个 异步任务完成才会走下 一个 。而我们把 task 拿出来了,也就是每个 task 会按照异步的方式去执行。这个时候三个 task 都已经开始执行了,当遇到 await 就只需要等到任务完成就行。所需要的时间是异步任务中耗时最长的,而不是之前的总和。

4. 小结

本节我们主要通过延续上一节的案例,用 async 函数 给出了最优的 解决方 案,从而完善了整个异步演变的过程,让我们更加清晰地理解为什么会有 Promise?为什么会有 生成 器?为什么会有 async/await?由浅入深层层递进地讲解了 ES6 以后对异步任务处理的演变。然后我们主要学习了 async 函数 的基本使用和 错误 处理的捕获。最后,我们讲解了如果不滥用 async 函数 的案例,让我们在以后写程序的过程中更加得心应手。

查看更多关于ES6+ async/await的详细内容...

  阅读:44次

上一篇

下一篇

第1节:ES6+ 简介    第2节:ES6 环境配置    第3节:ES6+ let    第4节:ES6+ const    第5节:ES6+ 展开语法    第6节:ES6+ 剩余参数    第7节:ES6+ 解构赋值    第8节:ES6+ 模版字符串    第9节:ES6+ 箭头函数    第10节:ES6+ 数值扩展    第11节:ES6+ isFinite()&isNaN()    第12节:ES6+ Number 对象的方法    第13节:ES6+ Math 对象的扩展    第14节:ES6+ includes()    第15节:ES6+ 字符串的扩展    第16节:ES6+ startsWith()    第17节:ES6+ endsWith()    第18节:ES6+ repeat()    第19节:ES6+ padStart()    第20节:ES6+ padEnd()    第21节:ES6+ trim()    第22节:ES6+ Array.from()    第23节:ES6+ of()    第24节:ES6+ find()和findIndex()    第25节:ES6+ copyWithin()    第26节:ES6+ fill()    第27节:ES6+ isArray()    第28节:ES6+ 对象的扩展    第29节:ES6+ flat()    第30节:ES6+ 可选链操作符    第31节:ES6+ Object.is()    第32节:ES6+ Object.assign()    第33节:ES6+ Object.keys()    第34节:ES6+ Object.values()    第35节:ES6+ Object.entries()    第36节:ES6+ 数据结构扩展    第37节:ES6+ Set    第38节:ES6+ WeakSet    第39节:ES6+ Map    第40节:ES6+ WeakMap    第41节:ES6+ Symbol    第42节:ES6+ for...of    第43节:ES6+ 迭代协议    第44节:ES6+ 实现一个简版的 Promise    第45节:ES6+ Promise 基础    第46节:ES6+ Promise 进阶    第47节:ES6+ Generator 基础    第48节:ES6+ Generator 函数应用    第49节:ES6+ async/await    第50节:ES6+ Class 前置知识    第51节:ES6+ Class    第52节:ES6+ Proxy    第53节:ES6+ Reflect(一)    第54节:ES6+ Reflect(二)    第55节:ES6+ 模块化(一)    第56节:ES6+ 模块化(二)    第57节:ES6实战1-实现Vue3 reactive 源码    第58节:ES6实战2-实现 Vue3 effect 源码    第59节:ES6 实战2-封装请求    第60节:ES6+ 实战3-代码整洁之道    第61节:ES6 Map原理分析    第62节:ES6module语法加载importexport    第63节:ES6的循环与可迭代对象示例详解