ES6 Promise 周全总结

ES6 Promise对象

ES6中,新增了Promise对象,它主要用于处置惩罚异步回调代码,让代码不至于堕入回调嵌套的绝路末路中。

@-v-@

1. Promise实质

Promise实质上是一个 函数 ,更确实地说,它是一个 组织器 ,特地用来组织对象的。
它接收一个函数作为参数,并返回一个对象,大抵状况以下:

  function Promise( fn ){
    // var this = {}
    // Object.setPrototypeOf(this, Promise.prototype)
    // 接下来,是Promise函数详细要完成的功用,这部分由体系帮我们完成
    ...  
    // 末了返回这个Promise实例
    return this
  }

Promise函数的参数,作为函数情势存在,须要我们手动去编写。
它须要两个参数,状况以下:

  function fn(resolve, reject){
    ...  // 我们本身写的逻辑代码
  }

Promise函数的返回值是一个对象,正确来讲,是Promise本身天生的实例。
实在Promise函数的使命,就是构建出它的实例,而且担任帮我们治理这些实例。
该实例有三种状况,分别是: 举行 状况、 完成 状况 和 失利 状况。
该实例只能从“ 举行 状况”转变成“ 完成 状况”,或从“ 举行 状况”转变成“ 失利 状况”,这个历程不可逆转,也不能够存在其他能够。由于Promise就是用来治理营业状况的一种机制,它能够保证营业的递次实行,而不涌现杂沓。

这就比方我们在家里炒一份菜,是只能够存在“ 正在炒菜 ”、“ 炒好了 ”和“ 炒糊了 ”这三个阶段的,而“正在炒菜”的状况肯定是会优先存在于“炒好了”和“炒糊了”两个状况前面,“炒好了”和“炒糊了”本身又是两个 互斥的事宜 ,所以这个历程,只能够涌现从“正在炒菜”状况过渡到“炒好了”或许“炒糊了”状况的状况,永久不能够从“炒好了”过渡到“炒糊了”状况,也不能够从“炒糊了”过渡到“炒好了”状况。

那末,这些由Promise函数构建出来的对象,终究有着什么用途呢?
我们先来看一组代码:

  fn( ( ( ( ()=>{} )=>{} )=>{} )=>{} )

像如许回调当中调回调的状况,在Node开辟中,是一件很罕见的事。
Node本身是一个无壅塞、无空耗、并发、依赖于体系底层读写事宜的运转环境,它的回调机制保证了它在异步并发实行历程当中回调链的独立性和抗干扰才能,但同时也带来了很大的副作用,最大的贫苦就是,采纳平常回调体式格局誊写出来的Node回调代码非常杂沓。

实在,面向历程或面向对象的函数式编程,本身就是一个庞大的“函数挪用”历程。我们在代码中运用函数,并在函数中挪用函数,运转环境协助我们保护一个或多个函数栈,以完成顺序的有序实行,及加强软件后期保护的便利性。

但假如我们能把这类不停挪用的历程给摊开成 平面 ,而不要使函数互相嵌套,就会使我们的软件可保护性提拔很大一个台阶。我们只须要将底本写好的功用一个个排列出来,并组织出一根供函数挪用的链条,把这些功用一个个地按需挪用,软件的功用不就完成了么?而且还更清楚清楚明了。
Promise协助我们将函数摊开来,构成一根挪用链条,让顺序有序实行。

每个返回值为Promise实例的函数,都是 Promise挪用链条上的一个结点 ,这个Promise实例保护着该处函数的运转状况,并决议着本身的生计周期。它的写法大抵是如许的:

  // 实行一个返回值为promise的函数 并经由过程resolve或reject返回
  promiseFn_1(...)
  // 将多个返回值为promise的函数合成一个 并经由过程resolve或reject返回
  // Promise.all( promiseFn_all_1, promiseFn_all_2, ... )
  // Promise.race( promiseFn_race_1, promiseFn_race_2, ... )
  //
  .then(
    (...resolveArgs)=>{ ... promiseFn_resolve_1(...) ... },
    (...rejectArgs)=>{ ... promiseFn_reject_1(...) ... },
  )
  .then(
    (...resolveArgs)=>{ ... promiseFn3_resolve_2(...) ... },
    (...rejectArgs)=>{ ... promiseFn3_reject_2(...) ... },
  )
  ...
  .catch(
    (...rejectArgs)=>{ ... promiseFn_catch_1(...) ... }
  )
  ...
  .finally(
    (...simpleArgs)=>{ ... }
  )

上面的代码看似及其烦琐,实在构造条理已比运用平常回调体式格局誊写的代码好很多了(虽然照样显得有些杂沓)。
当我们了解了Promise中这些函数(如then()、catch()、finally())的详细意义,就会邃晓它的详细意义了。
接下来我们就来构建一个Promise实例,看一看这根“链条”上的结点(也就是上面以“promiseFn_”开首的函数)究竟长什么样。

  function promiseFn_1(path, options){
    return new Promise((resolve,reject)=>{
      // 须要实行的详细代码,平常状况下,是挪用一个带有回调参数的函数
      // 此处运用fs模块中的readFile函数作为示例
      fs.readFile(path, options, (err,data)=>{
        if(err){
          reject(err)
          // 如许运用能够会更好:
          // throw new Error(path+' :  文件读取涌现未知的毛病!')
        }
        resolve(data)
      })
    })
  }

上面Promise参数函数中,涌现了两个生疏的参数,resolve和reject。它们实际上是在Promise运转完成后,主意向该回调函数中传入的参数。这个历程,由Promise函数自动帮我们完成。
resolve和reject都是与Promise实例相干的函数,用于转变Promise实例的状况。
resolve函数能使Promise实例从“举行”状况变成“完成”状况,并将本身接收到的参数传给下一个promise对象。
reject函数能使Promise实例从“举行”状况变成“失利”状况,并将本身接收到的参数传给下一个promise对象(平常是一个毛病对象)。

2. Promise的几个主要要领

2.1 promise Promise.prototype.then( resolveFn, [rejectFn] )

  @param resolveFn( ...args )  
    函数,当Promise实例状况变成“完成”状况时会被实行,  
    用于将从当前promise中掏出reresolve( ...args )中取得的参数(...args),  
    并举行响应的操纵,比方将(args)传入另一个封装了promise组织器的函数,  
    并将该函数实行完成后返回的promise实例返回  
    @param ...args  
      参数列表,当前promise实例处于“完成”状况时,经由过程resolve(...args)取得的值。  
  @param [rejectFn( ...args )]  
    函数,可选,当Promise实例状况变成“失利”状况时会被实行,  
    用于将从当前promise中掏出reject( ...args )中取得的参数(...args),  
    并举行响应的操纵,比方将(args)传入另一个封装了promise组织器的函数,  
    并将该函数实行完成后返回的promise实例返回  
    @param ...args  
      参数列表,当前promise处于“完成”状况时,经由过程resolve(...args)取得的值。  
  @return promise  
    promise对象,resolveFn或rejectFn实行后的返回值,  
    我们平常会在fn中挪用另一个封装了promise组织器的函数,  
    然后将其返回给then()要领,then()要领再将其作为then的返回值返回给当前链式挪用途,  
    假如fn()返回的不是一个promise对象,then()会帮我们将fn()返回值封装成promise对象,  
    如许,我们就能够确保能够链式挪用then()要领,并取得当前promise中取得的函数运转结果。  

then()要领定义在Promise.prototype上,用于为Promise实例增加状况变动时的回调函数,相当于监听一样。
当当前promise实例状况变成“完成”状况时,resolveFn函数自动实行。
当当前promise实例状况变成“失利”状况时,rejectFn函数自动实行。

2.2 promise Promise.prototype.catch( rejectFn )

  @param rejectFn( ...args )  
    函数,当Promise实例状况变成“失利”状况时会被实行,  
    用于将从当前promise中掏出reject( ...args )中取得的参数(...args),  
    并举行响应的操纵,比方将(args)传入另一个封装了promise组织器的函数,  
    并将该函数实行完成后返回的promise实例返回  
    @param ...args  
      参数列表,当前promise处于“完成”状况时,经由过程resolve(...args)取得的值。  
  @return promise  
    promise对象,rejectFn实行后的返回值,  
    假如fn()返回的不是一个promise对象,catch()会帮我们将fn()返回值封装成promise对象,  
    并将其返回,以确保promise能够被继承链式挪用下去。  

该要领实际上是“.then(null, rejectFn)”的别号,用于指定状况转为“失利”时的回调函数。
发起不要在then()要领中定义第二个参数,而应当运用catch(),构造条理会更好一些。
假如没有运用catch()要领指定毛病毛病处置惩罚的回调函数,promise实例抛出的毛病不会通报到外层代码。
假如promise状况已变成了resolved(“失利”状况),再抛出任何毛病,都是无效的。
promise实例中抛出的毛病具有冒泡的特征,它会一向向后通报,直到被捕获为止。

2.3 Promise.all( [promise1, promise2, …, promisen] )

  @param [promise1, promise2, ..., promisen]
    可遍历对象,一个由promise对象构成的可遍历对象,经常使用数组示意
  @return promise
    promise对象

Promise.all()用于将多个Promise实例包装成一个新的Promise实例,并返回。
Promise.all()要领接收一个由Promise实例构成的可遍历对象。假如可遍历对象中存在有不是Promise实例的元素,就会挪用Promise.resolve()要领,将其转为Promise实例。
本文的可遍历对象,指的是那些具有Iterator接口的对象,如Array、WeakSet、Map、Set、WeakMap等函数的实例。
Promise.all()要领返回的Promise实例的状况分红两种状况:

  • 可遍历对象中的Promise实例状况全变成 完成 状况时,该实例的状况才会转变成 完成 状况,此时,可遍历对象中的Promise实例的返回值会构成一个数组,传给该实例的回调。

  • 可遍历对象只需存在Promise实例状况转为 失利 状况时,该实例的状况就会转变成 失利 状况,此时,第一个转为 失利 状况的Promise实例的返回值会传给该实例的回调。

2.4 Promise.race( [promise1, promise2, …, promisen] )

  @param [promise1, promise2, ..., promisen]
    可遍历对象,一个由promise对象构成的可遍历对象,经常使用数组示意
  @return promise
    promise对象

Promise.race()与Promise.all()用法基础上一致,功用上也险些雷同,唯一的差别就是:
Promise.race()要领返回的Promise实例的状况分红两种状况:

  • 可遍历对象只需存在Promise实例状况转为 完成 状况时,该实例的状况才会转变成 完成 状况,此时,第一个转为 完成 状况的Promise实例的返回值,会作为该实例的then()要领的回调函数的参数。

  • 可遍历对象只需存在Promise实例状况转为 失利 状况时,该实例的状况就会转变成 失利 状况,此时,第一个转为 失利 状况的Promise实例的返回值,会作为该实例的then()要领的回调函数的参数。

2.5 promise Promise.resolve( notHaveThenMethodObject )

  @param notHaveThenMethodObject
    对象,一个原型链上不具有then()要领的对象
  @return promise
    promise对象

假如Promise.resolve()的参数的原型链上不具有then要领,则返回一个新的Promise实例,且其状况为 完成 状况,而且会将它的参数作为该实例的then()要领的回调函数的参数。
假如Promise.resolve()的参数是一个Promise实例(原型链上具有then要领),则将其一成不变地返回。
Promise.resolve()要领许可挪用时不运用任何参数。

2.6 promise Promise.reject( something )

  @param something
    恣意值,用于通报给返回值的then()要领的回调函数参数的值
  @return promise
    promise对象

Promise.reject要领的用法和resolve要领基础一样,只是它返回的Promise实例,状况都是 失利 状况。
Promise.reject要领的参数会被作为该实例的then()要领的回调函数的参数。
Promise.resolve()要领许可挪用时不运用任何参数。

Promise组织器回调函数参数中的 resolvereject 和Promise组织器要领中的 reject()resolve() 结果是不一样的。
Promise组织器回调函数参数中的 resolvereject 用于变动当前Promise的状况,并将其值返回给当前Promise的then()要领的参数。
Promise组织器要领中的 reject()resolve() 能够直接返回一个已转变状况的新的Promise对象。

  • Promise.reject() Promise.resolve()

  • new Promise((resolve, reject)=>{ resolve(…) 或 reject(…) })

2.7 Promise.prototype.done( [resolveFn], [rejectFn] )

  @param [resolveFn( ...args )]  
    函数,可选,当Promise实例状况变成“完成”状况时会被实行,  
    用于将从当前promise中掏出reresolve( ...args )中取得的参数(...args),  
    并举行响应的操纵,比方将(args)传入另一个封装了promise组织器的函数,  
    并将该函数实行完成后返回的promise实例返回  
    @param ...args  
      参数列表,当前promise实例处于“完成”状况时,经由过程resolve(...args)取得的值。  
  @param [rejectFn( ...args )]  
    函数,可选,当Promise实例状况变成“失利”状况时会被实行,  
    用于将从当前promise中掏出reject( ...args )中取得的参数(...args),  
    并举行响应的操纵,比方将(args)传入另一个封装了promise组织器的函数,  
    并将该函数实行完成后返回的promise实例返回  
    @param ...args  
      参数列表,当前promise处于“完成”状况时,经由过程resolve(...args)取得的值。  

不论以then()或catch()要领末端,若末了一个要领抛出毛病,则在内部能够没法捕获到该毛病,外界也没法取得,为了防止这类状况发作,Promise组织器的原型链上供应了done()要领。
promise.done()要领老是处于会调链的低端,它能够捕获就任安在回调链上抛出的毛病,并将其抛出。

2.8 Promise.prototype.finally( simpleFn )

  @param simpleFn  
    一个平常函数,这个平常函数无论怎样都邑被实行。  

finally要领指定,不论Promise对象末了状况怎样,都邑实行的操纵。

3. 代码参考

3.1 finally()的完成

  Promise.prototype.finally = function( simpleFn ){
    let Pro = this.constructor
    return this.then(
      value => Pro.resolve( simpleFn() ).then( () => value ),
      error => Pro.resolve( simpleFn() ).then( () => { throw error } )
    )
  }

3.2 done()的完成

  Promise.prototype.done = function( resolveFn, rejectFn ){
    this
      .then( resolveFn, rejectFn )
      .catch( error => {
        // 这是一个把须要实行的代码,从使命行列中拉出来的技能
        setTimeout( () => { throw error }, 0)
      } )
  }

这儿运用了一个很经常使用的技能:
我们来看一下这个例子:

  for(let i of [1,2,3]){
    setTimeout( () => { console.log( 'setTimeout ' + i ) }, 0)
    console.log( 'console ' + i )
  }

终究结果是:

  > console 1  
  > console 2  
  > console 3  
  > undefined  
  > setTimeout 1  
  > setTimeout 2  
  > setTimeout 3  

javascript除了保护着当前使命行列,还保护着一个setTimeout行列。一切未被实行的setTimeout使命,会按递次放到setTimeout行列中,守候平常使命行列中的使命实行完,才最先按递次实行积聚在setTimeout中的使命。
简而言之, javascript会在实行完当前使命行列中的使命后,再实行setTimeout行列中的使命
我们设置使命在0s后实行,能够将该使命调到setTimeout行列中,耽误该使命发作,使之异步实行。
这是异步实行计划当中,最经常使用,也最省时费事的一种体式格局。

3.3 加载图片

  function preloadImage(path){
    return new Promise( (resolve, reject) => {
      let img = document.createElement('img')
      img.style.display = 'none'
      document.body.appendChild(img)
      // 当图片加载完成后,promise转为完成状况
      // 此时,我们能够把该节点的图片加载在应有的处所,而且将其删除
      img.addEventListener('load', resolve)
      // 当图片加载失足后,promise转为失利状况
      img.addEventListener('error', reject)
      img.src = path
    } )
  }

3.4 Generator与Promise团结

  // Promise的包装函数 getFoo()
  function getFoo(){
    // ......something
    return new Promise( (resolve, reject) => {
      // ......something
      resolve('foo')
    } )
  }
  // Generator函数 generator()
  function* generator(){
    try{
      let foo = yield getFoo()
      console.log(foo)
    }
    catch(error){
      console.log(error)
    }
  }
  // 自动实行generator函数的函数,如今能够用async语法替换它
  function run(generator){
    // 让generator函数运转至第一个yield语句前,
    // 并取得getFoo()的结果---一个promise函数
    let it = generator()
    function go(result){
      if(result.done) return result.value
      return result.value.then( value => {
          // 应用尾递返来完成自动实行,让本次递归发生的栈单位项只要一个
          return go( it.next(value) )
        }, error => {
          return go( it.throw(error) )
        }
      )
    }
    go(it.next())
  }
  // 挪用run要领
  run(generator)
本文更新信息
  1. 易杭 2017/4/24 23:10 11840字

本文作者信息
  1. 易杭 迎接人人观光我的博客我的Github

支撑网站列表
  1. 易杭网 [www.freeedit.cn]

本文学问参考
  1. ES6规范入门第二版(阮一峰)

    原文作者:易杭
    原文地址: https://segmentfault.com/a/1190000009372893
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞