好得很程序员自学网

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

ES6+ Promise 进阶

ES6+ Promise 进阶

1. 前言

前两节我们学习了 Promise 的 用法 ,并且在上一节我们动手实现了 一个 符合 Promise A+ 规范的简版 Promise。真正了解了 Promise 底层是怎么来实现的,更好地帮助我们理解 Promise 并对 Promise 的扩张打下了基础。对 Promise 的扩展会可以 解决 一些通用的问题,比如使用 Promise.all() 去并发请求接口。在 node 中还提供了将 callback 类型的 api 转换为 Promise 对象。

本节我们将继续学习 Promise 对象相关API的使用。这些api在我们的实际应用中会经常使用,并且可以很好的 解决 常见的问题。

2. Promise.resolve () 和 Promise.reject ()

前面我们已经学习了在 new Promise() 对象时执行器会提供两个回调 函数 , 一个 是 resolve 返回 一个 立即成功的 Promise, 一个 是 reject 返回 一个 立即失败的 Promise。在执行器中需要根据不同情况调 resolve 或 reject ,如果我们只想返回 一个 成功或失败的 Promise 怎么做呢?

Promise 对象上的提供了 Promise.resolve(value) 和 Promise.reject(reason) 语法糖,用于只返回 一个 成功或失败的 Promise。下面我们看下它的对比写法:

  const  p1  =   new   Promise  (  function  ( resolve ,  reject )  { 
     res lov e  (  ) 
 }  ) 
 const  p2  =  Promise .  resolve  (  )   //和p1的写法一样 

 const  p3  =   new   Promise  (  function  ( resolve ,  reject )  { 
     reject  (  'error'  ) 
 }  ) 
 const  p4  =  Promise .  reject  (  'error'  )   //和p3的写法一样 
 

通过上面的对比 Promise.resolve(value) 创建的实例也具有 then 方法 的链式 调用 。这里有个概念就是:如果 一个 函数 或对象,具有 then 方法 ,那么他就是 thenable 对象。

 Promise .  resolve  (  )  .  then  (  ( value )   =>   { 
  console .  log  ( value )  ; 
 }  )  ; 

Promise .  reject  (  new   Error  (  'error'  )  )  .  then  (  (  )   =>   { 
   // 这里不会走 then 的成功回调 
 }  ,   ( err )   =>   { 
  console .  error  ( err )  ; 
 }  )  ; 
 

其实,实现 Promise.resolve(value) 和 Promise.reject(reason) 的源码是很简单的。就是在 Promise 类上创建 resolve 和 reject 这个两个 方法 ,然后去实例化 一个 Promise 对象,最后分别在执行器中的 resolve() 和 reject() 函数 。按照这个思路有如下实现方式:

  class   Promise   { 
	 ... 
   resolve  ( value )   { 
     return   new   Promise  (  ( resolve ,  reject )   =>   { 
       resolve  ( value ) 
     }  ) 
   } 
   reject  ( reason )   { 
     return   new   Promise  (  ( resolve ,  reject )   =>   { 
       reject  ( reason ) 
     }  ) 
   } 
 } 
 

通过上面的实现源码我们很容易地知道,这两个 方法 的 用法 。需要注意的是 Promise.resolve(value) 中的 value 是 一个 Promise 对象 或者 一个 thenable 对象, Promise.reject(reason) 传入的是 一个 异常的原因。

3. catch()

Promise 对象提供了链式 调用 的 catch 方法 捕获上一层 错误 ,并返回 一个 Promise 对象。catch 其实就是 then 的 一个 别名,目的是为了更好地捕获 错误 。它的行为和 Promise.prototype.then(undefined, onRejected) 只接收 onRejected 回调是相同的,then 第二个参数是捕获失败的回调。所以我们可以实现 一个 catch 的源码,如下:

  class   Promise   { 
   //... 
   catch  ( errorCallback )   { 
     return   this  .  then  (  null  ,  errorCallback )  ; 
   } 
 } 
 

从上面的实现 catch 的 方法 我们可以知道,catch 是内部 调用 了 then 方法 并把传入的回调传入到 then 的第二个参数中,并返回这个 Promise。这样我们就更清楚地知道 catch 的内部原理了,以后看到 catch 可以直接把它看成 调用 了 then 的失败的回调就行。下面我们看几个使用 catch 的例子:

  let  promise  =   new   Promise  (  ( resolve ,  reject )   =>   { 
   resolve  (  '100'  )  ; 
 }  ) 
promise .  then  (  ( data )   =>   { 
  console .  log  (  'data:'  ,  data )  ; 	 // data: 100 
   throw   new   Error  (  'error'  ) 
 }  ,   null  )  .  catch  ( reason  =>   { 
  console .  log  ( reason ) 	 // Error: error 
 }  ) 
 

catch 后还可以链式 调用 then 方法 , 默 认会返回 undefined。也可以返回 一个 普通的值或者是 一个 新的 Promise 实例。同样,在 catch 中如果返回的是 一个 普通值或者是 resolve,在下一层还是会被 then 的成功回调所捕获。如果在 catch 中抛出异常或是执行 reject 则会被下一层 then 的失败的回调所捕获。

 promise .  then  (  ( data )   =>   { 
  console .  log  (  'data:'  ,  data )  ; 	 // data: 100 
   throw   new   Error  (  'error'  ) 
 }  ,   null  )  .  catch  ( reason  =>   { 
  console .  log  ( reason ) 	 // Error: error 
   return  
 }  )  .  then  (  ( value )   =>   { 
  console .  log  ( value ) 	 // 200 
 }  ,   null  ) 
 

4. finally()

finally 是 ES9 的规范,它也是 then 的 一个 别名,只是这个 方法 是一定会执行的,不像上面提到的 catch 只有在上一层抛出异常或是执行 reject 时才会走到 catch 中。

 Promise .  resolve  (  '123'  )  .  finally  (  (  )   =>   { 
  console .  log  (  '100'  ) 	 // 100 
 }  ) 
 

知道 finally 是 then 的 一个 别名,那我们就知道在它后面也是可以链式 调用 的。

 Promise .  resolve  (  '123'  )  .  finally  (  (  )   =>   { 
  console .  log  (  '100'  ) 
   return  
 }  )  .  then  (  ( data )   =>   { 
  console .  log  ( data ) 	 // 123 
 }  ) 
 

需要注意的是在 finally 中返回的普通值或是返回 一个 Promise 对象,是不会传到下 一个 链式 调用 的 then 中的。如果 finally 中返回的是 一个 异步的 Promise 对象,那么链式 调用 的下一层 then 是要等待 finally 有返回结果后才会执行:

 Promise .  resolve  (  '123'  )  .  finally  (  (  )   =>   { 
  console .  log  (  '100'  ) 
   return   new   Promise  (  ( resolve ,  reject )   =>   { 
     setTimeout  (  (  )   =>   { 
       resolve  (  ) 
     }  ,   ) 
   }  ) 
 }  )  .  then  (  ( data )   =>   { 
  console .  log  ( data ) 	 // 123 
 }  ) 
 

执行上面的 代码 ,在 then 中打印的结果会在 3 秒 后执行 。这也说明了 finally 有类似 sleep 函数 的意思。

finally 是 ES9 的规范,在不兼容 ES9 的浏览器中就不能使用这个 api,所以我们可以在 Promise 对象的原型上 增加 一个 finally 方法 。

 Promise . prototype .  finally   =   function  ( callback )   { 
   return   this  .  then  (  ( value )   =>   { 
     return  Promise .  resolve  (  callback  (  )  )  .  then  (  (  )   =>  value )  ; 
   }  ,   ( err )   =>   { 
     return  Promise .  reject  (  callback  (  )  )  .  catch  (  (  )   =>   {  throw  err }  )  ; 
   }  ) 
 } 
 

因为 finally 是一定会执行的,所以 then 中的成功和失败的回调都需要执行 finally 的回调 函数 。使用 Promise.resolve(value) 和 Promise.reject(reason) 去执行 finally 传入的回调 函数 ,然后使用 then 和 catch 来返回 finally 上一层返回的结果。

3. Promise.all () 和 Promise.race ()

在前端面试中经常会问这两个 api 并做对比,因为它们的参数都是传入 一个 数组,都是做并发请求使用的。

3.1 Promise.all()

Promise.all() 特点是将多个 Promise 实例包装成 一个 新的 Promise 实例,只有同时成功才会返回成功的结果,如果有 一个 失败了就会返回失败,在使用 then 中拿到的也是 一个 数组,数组的顺序和传入的顺序是一致的。

  const  p1  =  Promse .  resolve  (  '任务1'  )  ; 
 const  p2  =  Promse .  resolve  (  '任务2'  )  ; 
 const  p3  =  Promse .  reject  (  '任务失败'  )  ; 

Promise .  all  (  [ p1 ,  p2 ]  )  .  then  (  ( res )   =>   { 
  console .  log  ( res )  ; 		 // ['任务1', '任务2'] 
 }  )  .  catch  (  ( error )   =>   { 
  console .  log  ( error ) 
 }  ) 

Promise .  all  (  [ p1 ,  p3 ,  p2 ]  )  .  then  (  ( result )   =>   { 
  console .  log  ( result ) 
 }  )  .  catch  (  ( error )   =>   { 
  console .  log  ( error )        // 任务失败 
 }  ) 
 

Promise.all() 在处理多个任务时是非常有用的,比如 Promise 基础 一节中使用 Promise.all() 并发的请求接口的案例,我们希望得到所以接口请求回来的数据之后再去做一些逻辑,这样我们就不需要维护 一个 数据来记录接口请求有没有完成,而且这样请求的好处是最大限度地利用浏览器的并发请求,节约时间。

3.2 Promise.race()

Promise.race() 和 Promise.all() 一样也是包装多个 Promise 实例,返回 一个 新的 Promise 实例,只是返回的结果不同。 Promise.all() 是所有的任务都处理完才会得到结果,而 Promise.race() 是只要任务成功就返回结果,无论结果是成功还是失败。

  const  p1  =   new   Promise  (  ( resolve ,  reject )   =>   { 
   setTimeout  (  (  )   =>   { 
     resolve  (  '任务1成功...'  )  ; 
   }  ,   ) 
 }  ) 
 const  p2  =   new   Promise  (  ( resolve ,  reject )   =>   { 
   setTimeout  (  (  )   =>   { 
     resolve  (  '任务2成功...'  )  ; 
   }  ,   ) 
 }  ) 
 const  p3  =   new   Promise  (  ( resolve ,  reject )   =>   { 
   setTimeout  (  (  )   =>   { 
     reject  (  '任务失败...'  )  ; 
   }  ,   ) 
 }  ) 

Promise .  race  (  [ p1 ,  p2 ]  )  .  then  (  ( res )   =>   { 
  console .  log  ( res )  ;     // 任务1成功... 
 }  )  .  catch  (  ( err )   =>   { 
  console .  log  ( err )  ; 
 }  ) 

Promise .  race  (  [ p1 ,  p2 ,  p3 ]  )  .  then  (  ( res )   =>   { 
  console .  log  ( res ) 
 }  )  .  catch  (  ( err )   =>   { 
  console .  log  ( err )    // 任务失败... 
 }  ) 
 

上面的实例 代码 充分的展示了 Promise.race() 特性,在实际的开发中很少用到这个 api,这个 api 能做什么用呢?其实这个 api 可以用在一些请求超时时的处理。

当我们浏览网页时,突然网络断开或是变得很差的情况下,可以用于 提示 用户 网络不佳,这也是 一个 比较常见的情况。这个时候我们就可以使用 Promise.race() 来处理:

  const  request  =   new   Promise  (  ( resolve ,  reject )   =>   { 
   setTimeout  (  (  )   =>   { 
     resolve  (  '请求成功...'  )  ; 
   }  ,   )  ; 
 }  ) 
 const  timeout  =   new   Promise  (  ( resolve ,  reject )   =>   { 
   setTimeout  (  (  )   =>   { 
     reject  (  '请求超时,请检查网络...'  )  ; 
   }  ,   )  ; 
 }  ) 

Promise .  race  (  [ request ,  timeout ]  )  .  then  ( res  =>   { 
  console .  log  ( res )  ; 
 }  ,  err  =>   { 
  console .  log  ( err )  ;     // 请求超时,请检查网络... 
 }  ) 
 

上面的 代码 中定义了两个 Promise 实例, 一个 是请求实例, 一个 是超时实例。请求实例当 3 秒的时候才会返回,而超时设置了 2 秒,所以会先返回超时的结果,这样就可以去提醒 用户 了。

3.3 实现 Promise.all ()

面试题:实现 一个 Promise.all() 方法 。

前面我们说到了 thenable 对象,也就是判断 一个 值是不是 Promise 对象,就是判断它是 函数 或对象,并具有 then 方法 。

  const   isPromise   =   ( val )   =>   { 
   if   (  typeof  val  ===   "function"   ||   (  typeof  val  ==   "object"   &&  val  !==   null  )  )   { 
     if   (  typeof  val . then  ===   "function"  )   { 
       return   true  ; 
     } 
   } 
   return   false  ; 
 }  ; 
 

Promise.all() 会接收 一个 数组,数组的每一项都是 一个 Promise 实例,并且它的返回结果也是 一个 Promise,所以我们需要在内部 new 一个 Promise 对象,并返回。在执行器中我们的目标是:

当有实例中有 错误 或抛出异常时,就要执行执行器中的 reject; 没有 错误 时,只有所有的实例都成功时才会执行执行器中的 resolve。

基于这两点,有如下步骤:

内部创建 一个 计数器,用于记住已经处理的实例,当计数的值和传入实例的数组长度相等时,执行执行器中的 resolve; 创建 一个 用于存放实例返回结果的数组; 处理实例的结果有两种:一种返回的是普通值、一种返回的是 Promise 对象,然后分别处理; 返回普通值结果时直接存放到数组中即可; 返回的是 一个 Promise 对象时,就需要 调用 这个实例上的 then 方法 得到结果后在存放到结果数组中去。

根据上面的五个步骤基本就可以把 Promise.all() 实现出来了,具体 代码 如下:

 Promise .  all   =   function  ( arr )   { 
   return   new   Promise  (  ( resolve ,  reject )   =>   { 
     let  num  =   ;    // 用于计数 
     const  newArr  =   [  ]  ;    // 存放最终的结果 

     function   processValue  ( index ,  value )   { 	 // 处理Promise实例传入的结果 
      newArr [ index ]   =  value ; 
       if   (  ++ num  ==  arr . length )   { 	 // 当计数器的值和处理的 Promise 实例的长度相当时统一返回保护所以结果的数组 
         resolve  ( newArr )  ; 
       } 
     } 

     for   (  let  i  =   ;  i  <  arr . length ;  i ++  )   { 
       const  currentValue  =  arr [ i ]  ;    // Promise 实例 
       if   (  isPromise  ( currentValue )  )   { 
        currentValue .  then  (  ( res )   =>   { 
           processValue  ( i ,  res )  ; 
         }  ,  reject ) 
       }   else   { 
         processValue  ( i ,  currentValue )  ; 
       } 
     } 
   }  )  ; 
 } 
 

上面的 代码 已经实现了 Promise.all() 方法 ,可以使用下面的例子进行测试。

  const  p1  =   new   Promise  (  ( resolve ,  reject )   =>   { 
   setTimeout  (  (  )   =>   { 
     resolve  (  "任务1成功..."  )  ; 
   }  ,   )  ; 
 }  )  ; 

 const  p2  =   new   Promise  (  ( resolve ,  reject )   =>   { 
   setTimeout  (  (  )   =>   { 
     resolve  (  "任务2成功..."  )  ; 
   }  ,   )  ; 
 }  )  ; 

Promise .  all  (  [ p1 ,  p2 ]  )  .  then  (  ( res )   =>   { 
  console .  log  ( res ) 
 }  ) 
 

4. 小结

本节学习了根据 Promise 衍生出的相关 api 的使用,已经每个 api 基本都给出了实现源码,理解这些源码会让我们更加深刻地理解 Promise,在实际的开发过程中达到游刃有余。到此我们花了三节的时间由浅入深来介绍 Promise,花些时间来彻底弄懂这些知识点,对于我们以后学习其他的异步 解决方 案有更好的理解。

查看更多关于ES6+ Promise 进阶的详细内容...

  阅读:42次

上一篇

下一篇

第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的循环与可迭代对象示例详解