ES6 Promise 全面总结

ES6 Promise对象

ES6中,新增了Promise对象,它主要用于处理异步回调代码,让代码不至于陷入回调嵌套的死路中。javascript

@-v-@html


1. Promise本质

Promise本质上是一个 函数 ,更确切地说,它是一个 构造器 ,专门用来构造对象的。
它接受一个函数做为参数,并返回一个对象,大体状况以下:java

function Promise( fn ){
    // var this = {}
    // Object.setPrototypeOf(this, Promise.prototype)
    // 接下来,是Promise函数具体要实现的功能,这部分由系统帮咱们完成
    ...  
    // 最后返回这个Promise实例
    return this
  }

Promise函数的参数,做为函数形式存在,须要咱们手动去编写。
它须要两个参数,状况以下:git

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

Promise函数的返回值是一个对象,准确来讲,是Promise本身生成的实例。
其实Promise函数的使命,就是构建出它的实例,而且负责帮咱们管理这些实例。
该实例有三种状态,分别是: 进行 状态、 完成 状态 和 失败 状态。
该实例只能从“ 进行 状态”转变为“ 完成 状态”,或从“ 进行 状态”转变为“ 失败 状态”,这个过程不可逆转,也不可能存在其余可能。由于Promise就是用来管理业务状态的一种机制,它可以保证业务的顺序执行,而不出现混乱。 es6

这就比如咱们在家里炒一份菜,是只可能存在“ 正在炒菜 ”、“ 炒好了 ”和“ 炒糊了 ”这三个阶段的,而“正在炒菜”的状态确定是会优先存在于“炒好了”和“炒糊了”两个状态前面,“炒好了”和“炒糊了”自己又是两个 互斥的事件 ,因此这个过程,只可能出现从“正在炒菜”状态过渡到“炒好了”或者“炒糊了”状态的状况,永远不可能从“炒好了”过渡到“炒糊了”状态,也不可能从“炒糊了”过渡到“炒好了”状态。 github

那么,这些由Promise函数构建出来的对象,究竟有着什么用处呢?
咱们先来看一组代码:编程

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

像这样回调之中调回调的状况,在Node开发中,是一件很常见的事。
Node自己是一个无阻塞、无空耗、并发、依赖于系统底层读写事件的运行环境,它的回调机制保证了它在异步并发执行过程当中回调链的独立性和抗干扰能力,但同时也带来了很大的反作用,最大的麻烦就是,采用普通回调方式书写出来的Node回调代码十分混乱。 数组

其实,面向过程或面向对象的函数式编程,自己就是一个巨大的“函数调用”过程。咱们在代码中使用函数,并在函数中调用函数,运行环境帮助咱们维护一个或多个函数栈,以实现程序的有序执行,及加强软件后期维护的便利性。 promise

但若是咱们能把这种不断调用的过程给摊开成 平面 ,而不要使函数相互嵌套,就会使咱们的软件可维护性提高很大一个台阶。咱们只须要将本来写好的功能一个个罗列出来,并构造出一根供函数调用的链条,把这些功能一个个地按需调用,软件的功能不就实现了么?并且还更清晰明了。
Promise帮助咱们将函数摊开来,造成一根调用链条,让程序有序执行。 bash

每个返回值为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标准入门第二版(阮一峰)

相关文章
相关标签/搜索