ES6 Promise 用法和执行流程解析(附Promise代码仿写)

零、目录

  1. Promise 介绍
  2. Promise的简单使用例子
  3. then 方法在Promise异步和同步状态下执行时的具体顺序
  4. promise的两种使用方式以及具体应用场景
  5. Promise 的代码实现仿写

一、Promise 介绍

Promise 是在 es6 提供给开发者的一种用于异步编程的解决方案, 通过 Promise 可以更为方便且优雅的解决复杂异步问题, 同时 Promise通过代码的纵向拓展解决了回调地狱的问题。

1.Promise 内部构造

Promise对象的内部存在但不局限于以下四个成员(以下四个最为核心, 其中resolve, reject两个接口并不直接挂载在Promise的对象上, 而是被暴露给了执行器函数)如图 1.1      ES6 Promise 用法和执行流程解析(附Promise代码仿写)

        (图1.1 Promise 内部构造简图)

(1) PromiseState: 用于记录 Promise 对象的状态, 它只有以下三种类型的值

  • pending:未执行resolve 或 reject 方法(未确定Promise此时的状态)
  • fulfilled: 执行器 执行了resolve 方法
  • rejected:执行器 执行了reject方法, 或者在执行器内部抛出了异常

(2) PromiseResult: 记录Promise的状态发生跃迁之后, resolve 和 reject 所传递的参数值

(3) resolve: 一个触发函数, 这是用于将该 promise 对象状态迁移成 fulfilled状态的一个接口, resolve函数所接收的参数, 会成为该promiseState的值

(4) reject:   同resolve一样, 是一个用于跃迁状态的接口, 它会将Promise的状态变更为 rejected, 同时它所接收到的参数, 会被保存在PromiseState里

注: 对于一个 Promise 对象而言, 它仅会发生一次状态跃迁,而且其状态只能从 pending迁移到 fullfiled 或者 rejected状态 , 如图 1.2。

ES6 Promise 用法和执行流程解析(附Promise代码仿写)

                                (图1.2 Promise 状态跃迁图)

        Promise 在初始化的时候,会接收一个函数,在这里我们称之为执行器函数 executor, Promise 通过将 resolve和reject这两个用于改变状态的接口传递给executor函数。 这样, 我们就        可以在 executor中调用这两个接口来改变Promise状态。

        Promise 一经定义, 就会立即执行 executor 函数,(如果 executor 函数是个异步函数的话,后续动作则会被推送到执行队列延后执行, 具体流程在后边细谈),Promise在 executor 函数中的resolve、reject函数被执行或者抛出异常时, 即刻发生状态跃迁。

这里值得注意的两点是

  • 即使resolve或者reject函数被调用了, executor 函数仍然会执行到其整个上下文结束,换句话说,整个 executor 函数并不会被resolve或是reject函数中断
  • Promise状态由第一个执行的resolve或者reject来决定, 即当一个 executor 先执行了 resolve函数, 那么即使随后又执行了reject函数,其状态也不再会发生改变, 反之亦然。

2.then方法

        Promise一般都伴随着 then方法来使用, 当Promise触发状态跃迁时, 触发状态跃迁的 resolve或reject函数所接收到的参数,会成为 PromiseResult的值, 并且这个值在之后会被作为参数传递给 then方法,then方法的返回值仍然是一个Promise对象。

ES6 Promise 用法和执行流程解析(附Promise代码仿写)

         (图2.1 Promise和then函数配合执行简图)

        then方法接收一个参数为resolve 和 reject的函数, 当Promise状态跃迁为 fulfilled(rejected)时,后续就会执行then方法中的resolve(reject)函数,(这看起来有点像订阅发布(观察者)模式,当然其中也有所不同。 区别在于,当Promise在 executor 执行器为同步函数时的,then方法中的resolve和reject的触发并不是即刻生效的, 它会等到 js代码执行到 then方法的词法作用域时,才会触发。

二、Promise的简单使用例子

我们用以下一个简答的例子来举例 Promise的使用流程。

let promise = new Promise((resolve, reject) => {
  // 用一个定时器, 模拟 ajax请求登录
  setTimeout(() => {
    resolve({
      name: 'remiliko',
    })
  })
})
promise.then(data => {
  console.log('welcome', data) // welcome remiliko
}, err => {
  console.log('login err')
}

三、then 方法在Promise异步和同步状态下执行时的具体顺序

        then方法实际执行的顺序 取决于Promise的 executor 是同步函数或是异步函数(这个问题等同于then方法执行在Promise的状态发生跃迁之前还是之后, 当然这两种情况都是可能发生的)。

1.promise状态跃迁先于then方法执行

executor 函数为同步函数时候,执行流程很简单, 如图 3.1所示

ES6 Promise 用法和执行流程解析(附Promise代码仿写)

(图3.1 promise状态跃迁先于then方法执行)

        当 executor 函数为同步函数时, Promise的执行顺序和普通代码块并没有太多区别, 就像流水线一样, 直到代码执行到then方法的词法作用域,才会触发。

2.Promise状态跃迁慢于then方法执行

        在Promise之中, 维护了一个专门存储 then方法的队列(更具体来说存储的是then方法的resolve和reject参数),当程序执行到了then 方法的词法作用域的时候, Promise的状态仍然为 Pending状态, 那么此刻, 该then方法的resolve和reject都会被挂载到Promise身上的任务队列之中, 等到Promise状态一旦发生改变, 就立即执行, 而这也就是一个典型的订阅发布(观察者模型)模型, 具体流程如图3.2所示。

ES6 Promise 用法和执行流程解析(附Promise代码仿写)

(图3.2 Promise状态跃迁晚于then方法执行)

四、promise的两种使用方式以及具体应用场景

我们通过具体应用场景的方式来阐释其对应的使用方法(应用场景仅仅为了恰当解释问题, 不一定是最优解法)

1.对同一Promise施加多个then方法

Promise支持对同一个Promise对象同时执行多个then函数,它的形态大抵如图4.1所示

ES6 Promise 用法和执行流程解析(附Promise代码仿写)

        (图4.1 对同一个Promise施加多个then方法)

由于这一特性的存在,使得我们可以很容易的实现广播的效果,例如

比如我们在处理一个登录事件, 当登陆事件触发时, 我们需要执行刷新购物车,刷新导航栏等业务代码,那么我们就可以利用这一特性, 模拟代码如下

let promise = new Promise((resolve, reject) => {
  // 用一个定时器, 模拟 ajax请求登录
  setTimeout(() => {
    resolve({
      name: 'remiliko',
    })
  })
})
promise.then(data => {
  console.log('刷新导航栏', data) // 刷新导航栏 remiliko
}, err => {
  console.log('login err')
})
promise.then(data => {
  console.log('刷新购物车', data) // 刷新购物车 remiliko
}, err => {
  console.log('login err')
})
promise.then(data => {
  /* ...statement... */
}, err => {
  console.log('login err')
})

2. promise的链式调用(也称作级联调用)

        之所以能够链式调用then方法,是因为 Promise的then方法用一个Promise将其返回值封装了起来,这样then方法的返回值也会是一个Promise,自然可以执行then方法, then方法返回的promise的状态触发情况有如下以下几种

  • pending : then方法所对应的promise对象,并未发生状态跃迁, 那么then方法会被挂起, 并不会执行
  • fulfilled: then方法中无论是执行了resolve函数 还是reject函数, 如果没有抛出异常,或者显示的返回一个状态为rejected的Promise对象, 那么then方法返回的Promise状态都会是 fulfilled, 这个Promise对象对其使用then方法, 它会得到来自上一个then方法所返回的值, 如果上一个then方法没有返回值, 那么会得到一个PromiseResult为undefined的对象。
  • rejected: then方法中的resolve或者reject函数显示的返回了一个状态为rejected的promise对象,或者抛出了异常

Promise链式调用的执行图大抵如图4.2所示

 

ES6 Promise 用法和执行流程解析(附Promise代码仿写)

                                (图4.2 Promise的链式调用)

        then方法选择调用resolve方法,还是reject方法, 主要取决于它所参照的Promise身上状态而言。而对then方法返回的Promise对象,再次执行then方法,依然会参照以上规则, 整个执行流程看起来有点像简化版的某种循环系统。

(有一个常用的技巧是,由于Promise内部对每个resolve和reject函数都用try catch包裹了起来,因此对于一个链式的promise队列,其具备异常穿透的特性, 我们可以不必对每一个then方法都进行错误判断,取而代之的是用一个catch函数放置在then队列的最末尾处, 这样无论异常出现在哪一个then方法之中都会被传递到最后catch方法之中)

Promise的链式调用通常会出现在阶段性请求的场景之中,我们拿以下一个简单的例子举例

比如,有的网站需要先登录用户,进而判断用户权限, 然后再去请求某些权限数据.....

同样的我们会使用setTimeout方法模拟AJAX请求,代码如下

let Promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    // 登录用户, 并传入 id 信息
    resolve({name:'remiliko', id: '23888'})
  }, 1 * 1000);
}).then(data => {
  // 提取出 id 信息, 去查询用户的购物车
    let { id } = data
    setTimeout(() => {
      resolve({ commodity : [
        {item: '1'},
        {item: '2'}
      ]})
    }, 1 * 1000)
}).then(data=> {
  // 在这里将商品信息统一呈现到前端界面
}).catch(err => {
  // 请求失败的同一处理
  console.log(err)
})  

五、Promise 的代码实现仿写

/* 这里仅仅实现最基础的功能 */

function mPromise(executor) {
  if (!executor instanceof Function)
    return console.warn('executor is no a function');
  this.PromiseState = 'pending'; // 记录 Promise 状态
  this.PromiseResult = null; // 用于存储 Promise 状态跃迁后, 得到的结果
  executor(this.resolve.bind(this), this.rejected.bind(this));
}
// 用于将 Promise 状态变更为 fullfilled 的钥匙
mPromise.prototype.resolve = function (data) {
  this.PromiseState = 'fulfilled';
  this.PromiseResult = data;
  if (this.chain) {
    // 执行多任务请求
    for (let i = 0, fns; (fns = this.chain[i++]); ) {
      if (this.PromiseState == 'fulfilled') {
        this.PromiseResult = fns.doResolve(this.PromiseResult);
      }
    }
  }
};
​
mPromise.prototype.rejected = function (data) {
  this.PromiseState = 'rejected';
  this.PromiseResult = data;
  if (this.chain) {
    for (let i = 0, fns; (fns = this.chain[i++]); ) {
      if (this.PromiseState == 'rejected') {
        this.PromiseResult = fns.doReject(this.PromiseResult);
      }   
    }
  }
};
mPromise.prototype.then = function(doResolve, doReject) {
  return new mPromise((resolve, reject) => {
    // 用于判断 then 方法返回的结果是一个 promise 还是 其他值
    let dispatch = (adapter) => {
      try { 
        let res = adapter(this.PromiseResult)
        if(res instanceof mPromise) {
          //  由于需要考虑 promise是同步或者异步, 因此统一借助 then 方法来变更状态
          res.then(v => resolve(v), r => reject(r))
        } else {
          // 对于普通对象
          resolve(this.PromiseResult)
        }
      } catch(err) {
        // 收集异常, 并改变 then方法返回的 Promise状态
        reject(err)
      }
    }
​
    if(this.PromiseState == 'fulfilled') {
      // 为了保证 then 方法执行时机的统一都是异步执行
      setTimeout(() => {
        dispatch(doResolve) 
      })
    }
    else if(this.PromiseState == 'rejected') {
      setTimeout(()=> {
        dispatch(doReject)
      })
    }
    // 当js执行到 then方法的词法作用域, 而Promise状态为pending, 将then方法挂载到执行队列
    else {
      let self = this
      // 为了保证可以执行多个 then
      if(!this.chain)
        this.chain = []
      this.chain.push({
        doResolve: function() {
          dispatch(doResolve)
        },
        doReject: function() {
          dispatch(doReject)
        }
      })
    }
  })
}

上一篇:k8s配置私有仓库和自动升级和回滚


下一篇:linux 中sort命令 -k选项