好得很程序员自学网

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

ES6+ Promise 基础

ES6+ Promise 基础

1. 前言

我们知道浏览器在渲染网页时,会创建 一个 渲染进程进行渲染 页面 ,在渲染进程中其中有 GUI 渲染线程和 JS 引擎线程(如 V8 引擎)两个线程是互斥的。也就是说在同一时间内只能有 一个 线程执行。如果 JavaScript 执行一段耗时程序时会阻止 页面 渲染。如果要 页面 快速 在 用户 面前呈现就 要做 一些优化处理。对于不能立马得到结果的程序,不需要等待,可以放到事件队列中,等到得到结果后再执行。

对这种不等待方式,JavaScript 提供了异步的 解决方 案,在 JavaScript 中常见的异步 解决方 案是 Callback 方式,而像 setTimeout 这样提供异步的 API,还可以使用发布 订阅 的来实现异步。使用回调 函数 存在回调地狱的问题。为了 解决 回调地狱,最早社区提出了 Promise 概念。最后在 ES6 时正式作为官方的 解决方 案,说明 Promise 有它独有的优势。本节我们将学习 Promise 的基本 用法 。

2. 回调地狱

我们都知道 JavaScript 异步使用的是回调 函数 ,下面我们来看 一个 ajax 请求的实例,下面的 ajax 方法 是 一个 伪 代码 ,可以看作是请求接口的 方法 ,接口请求的库可以参考 jQuery 的 $.ajax 和 axios。

  // ajax请求的伪 代码  
 function   ajax  ( url ,  sucessCallback ,  failCallback )   { 
   // url:请求的url 
   // sucessCallback:成功的回调 函数  
   // failCallback:失败的回调 函数  
 } 

 ajax  ( url1 ,   ( res1 )   =>   { 
   ajax  ( url2 ,   ( res2 )   =>   { 
     ajax  ( url3 ,   ( res3 )   =>   { 
		 doSomething  ( res1 ,  res2 ,  res3 ) 
     }  ) 
   }  ) 
 }  ) 
 

上面的 ajax 请求我们可以理解为,在 调用 doSomething 方法 时需要前面三个请求的结果作为参数,所以只有前 一个 ajax 请求得到结果后才能发起第二个请求。这样前后有依赖的嵌套被称为回调地狱。对于比较复杂逻辑的情况来说,回调地狱会使程序出问题的概率大大 增加 。

另外,这样做有个很严重的问题,就是接口请求的时间是三个请求的和,不能进行并发操作,当然我们也可以做一些优化操作,如下:

  let  out  =   after  (  ,   function   ( data )  { 
   doSomething  (  ... data ) 
 }  ) 

 ajax  ( url1 ,   ( res1 )   =>   { 
   out  ( res1 ) 
 }  ) 
 ajax  ( url2 ,   ( res2 )   =>   { 
   out  ( res2 ) 
 }  ) 
 ajax  ( url3 ,   ( res3 )   =>   { 
   out  ( res3 ) 
 }  ) 

 function   after  ( times ,  callback )   { 
   const  arr  =   [  ]  ; 
   return   function   ( value )  { 
    arr .  push  ( value )  ; 
     if   (  -- times ==  )   { 
       callback  ( arr )  ; 
     } 
   } 
 } 
 

上面的 代码 很优雅地 解决 了回调嵌套的问题,但同时我们需要手动维护 一个 计数器来控制最后的回调。这无疑 增加 了程序的复杂度,我们更希望的是关注我的业务,而不是写更多的逻辑来优化。

针对这种情况,社区提供了很多这类优化的库,而 Promise 则是其中最亮眼的。对上面的情况,Promise 怎么 解决 的呢?看如下的实现方式:

  function   request  ( url )   { 
   return   new   Promise  (  ( resolve ,  reject )   =>   { 
     ajax  ( url ,   ( res )   =>   { 
       resolve  ( res ) 
     }  ) 
   }  ) 
 } 
Promise .  all  (  [  request  ( url1 )  ,   request  ( url1 )  ,   request  ( url1 )  ]  )  .  then  (  ( result )   =>   { 
   doSomething  (  ... result ) 
 }  )  .  catch  (  ( error )   =>   { 
  console .  log  ( error ) 
 }  ) 
 

上面的 代码 中我们封装了 一个 request 请求的 方法 ,通过 Promise.all() 来并发请求这些接口,当接口都正确返回才会执行 then 方法 中的回调,有 一个 错误 都会抛出异常。这种方式比较好的是,我们对请求进行了封装,不要再关注每一步请求是否完成做对应的逻辑处理,让我们在开发过程中更加关注业务逻辑,使开发效率更快。

3. Promise 用法

前面我们通过 一个 回调地狱的案例,说明了 Promise 的优点,就是为了 解决 异步而产生的。并且可以处理并发请求,很好地优化了程序资源。

3.1 实例化 一个 Promise

首先需要明确 Promise 是 一个 类,我们在 VSCode 中输入 new Promise() 会给我们如下的 提示 :

在 new Promise() 时需要 默 认需要传入 一个 回调 函数 ,这个回调 函数 是 executor(执行器), 默 认会立即执行。执行器会提供两个 方法 (resolve 和 reject)用于改变 promise 的状态。 resolve 会触发成功状态, reject 会触发失败状态,无论成功或失败都会传入 一个 返回值,这个返回值会在实例 调用 then 方法 后作为响应值 获取 。

  var  promise  =   new   Promise  (  ( resolve ,  reject )   =>   { 
   ajax  ( url ,   ( data )   =>   { 
     resolve  ( data ) 	 // 成功 
   }  ,   ( error )   =>   { 
     reject  ( error ) 	 // 失败 
   }  ) 
 }  ) 
 

上面的 代码 中实例化 一个 ajax 请求的 Promise, 当接口请求成功就会 调用 resolve () 方法 把请求的值传入进去,如果失败了就 调用 reject () 方法 把 错误 信息传入进去。在后续的链式 调用 中 获取 相应的结果。

我们需要知道的是,Promise 有三个状态:等待(padding)、成功(fulfilled),失败(rejected)。在初始化时,这个状态是等待态,在等待状态时可以转化为成功态或失败态。当状态是成功态或是失败态后不能再被改变了。

上面的 代码 中可以改变 Promise 状态的是执行器提供的 resolve 和 reject,resolve 会将等待态变为成功态,reject 则会将等待态变为失败态,在状态变为成功或失败的状态时就不能被更改了。

3.2 then

在实例化 Promise 类后,我们如何访问成功和失败的值呢?Promise 提供了链式 调用 的 then 方法 用于访问成功的值和失败的值,then 提供了两个回调 onfulfilled(成功回调)、onrejected(失败回调)。

  var  promise  =   new   Promise  (  ( resolve ,  reject )   =>   { 
   resolve  (  )  ; 
   // reject('error') 
   // throw new Error('Error') 
 }  ) 

promise
	 .  then  ( 
     ( data )   =>   { 
      console .  log  ( data ) 	 // 123 
       return   '100' 
     }  , 
     ( reason )   =>   { 
      console .  log  ( reason ) 
     } 
   ) 
   .  then  (  ( data )   =>   { 
      console .  log  ( data ) 	 // 100 
   }  ,   null  ) 
 

上面的 代码 中给我了几个测试用例,有兴趣的小伙伴可以进行测试。then 方法 返回 一个 值而不是 Promise 实例,并且会把这个结果返回到下 一个 then 的成功回调中;

如果返回的是 一个 promise,下 一个 then 会采用这个 promise 结果,如果返回的是失败,会传到下 一个 then 中失败的回调 函数 中去:

  var  promise  =   new   Promise  (  ( resolve ,  reject )   =>   { 
   resolve  (  )  ; 
 }  ) 

promise
	 .  then  ( 
     ( data )   =>   { 
       return   new   Promise  (  ( resolve ,  reject )   =>   { 
         reject  (  ' 错误  内容 '  )  ; 
       }  ) 
     }  , 
     null 
   ) 
   .  then  (  null  ,   ( err )   =>   { 
      console .  log  (  'error:'  ,  err ) 	 // error:  错误  内容  
   }  ) 
 

如果在失败的回调 函数 中返回 一个 普通值或成功的 promise 也会走到下一层 then 的成功回调中去。

 promise .  then  (  null  ,   ( err )   =>   { 
	 return   '100'  ; 
 }  )  .  then  (  ( data )   =>   { 
  console .  log  (  'data:'  ,  data )  ; 	 // data: 123 
 }  ,   null  ) 
 

通过上面的例子可以知道,当前 then 中走成功与否,主要看上一层返回的结果。总结有两点。

当上一层返回 一个 普通值,或是 一个 成功的 Promise,则会走到下一层成功的回调 函数 中去; 如果上一层返回 一个 失败的 Promise,或是抛出 一个 异常,则会走到下一层的失败的回调 函数 中去。

4. 小结

本节主要通过 JavaScript 中回调地狱的 一个 案例来引出为什么使用 Promise,以及 Promise 所带来的好处。然后学习了 Promise 的基本使用和链式 调用 then 方法 ,需要注意的是,then 中执行成功或是失败是根据它上一层的返回值,如果返回的是 一个 普通值或成功的 Promise 则会走 then 的成功回调;如果抛出异常或返回失败的 Promise 则走 then 的失败回调。

查看更多关于ES6+ Promise 基础的详细内容...

  阅读:39次

上一篇

下一篇

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