资讯专栏INFORMATION COLUMN

ES6 Promise 全面总结

Towers / 2664人阅读

摘要:它们其实是在运行完成后,主动向该回调函数中传入的参数。该方法其实是的别名,用于指定状态转为失败时的回调函数。构造器回调函数参数中的和用于更改当前的状态,并将其值返回给当前的方法的参数。

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)
本文更新信息

易杭 2017/4/24 23:10 11840字

本文作者信息

易杭 欢迎大家参观我的博客和我的Github

支持网站列表

易杭网 [www.freeedit.cn]

本文知识参考

ES6标准入门第二版(阮一峰)

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/82935.html

相关文章

  • ES6-7

    摘要:的翻译文档由的维护很多人说,阮老师已经有一本关于的书了入门,觉得看看这本书就足够了。前端的异步解决方案之和异步编程模式在前端开发过程中,显得越来越重要。为了让编程更美好,我们就需要引入来降低异步编程的复杂性。 JavaScript Promise 迷你书(中文版) 超详细介绍promise的gitbook,看完再不会promise...... 本书的目的是以目前还在制定中的ECMASc...

    mudiyouyou 评论0 收藏0
  • ES6Promise:要优雅,也要浪漫

    摘要:就算改变已经发生了,即使再对对象添加回调函数,也会立即得到这个结果。方法接收个参数,第一个参数是状态的回调函数,第二个参数可选是状态的回调函数。简单来讲,就是能把原来的回调写法分离出来,在异步操作执行完后,用链式调用的方式执行回调函数。 在ECMAScript 6标准中,Promise被正式列为规范,Promise,字面意思就是许诺,承诺,嘿,听着是不是很浪漫的说?我们来探究一下这个浪...

    weizx 评论0 收藏0
  • 异步和promise

    摘要:首先构造函数中需要有一些状态和方法因为执行实例逻辑的时候需要这些维护好的状态和值其中着重提醒的就是的状态机是单向的且状态单向不可逆。 引言 16年时在公司分享过一次promise,犹记得当时是第一次分享,还蛮紧张的,当时分享的主要是promise的使用和基本原理,后来又给无线部门同学分享了一次。现在回顾想想,其实讲的不是很完美,因为我当时的实现方式类似于简化版q库的实现,考虑的也不全面...

    libxd 评论0 收藏0
  • H5学习

    摘要:为此决定自研一个富文本编辑器。本文,主要介绍如何实现富文本编辑器,和解决一些不同浏览器和设备之间的。 对ES6Generator函数的理解 Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。 JavaScript 设计模式 ② 巧用工厂模式和创建者模式 我为什么把他们两个放在一起讲?我觉得这两个设计模式有相似之处,有时候会一个设计模式不能满...

    aristark 评论0 收藏0
  • H5学习

    摘要:为此决定自研一个富文本编辑器。本文,主要介绍如何实现富文本编辑器,和解决一些不同浏览器和设备之间的。 对ES6Generator函数的理解 Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。 JavaScript 设计模式 ② 巧用工厂模式和创建者模式 我为什么把他们两个放在一起讲?我觉得这两个设计模式有相似之处,有时候会一个设计模式不能满...

    shevy 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<