关于前端:Promiz初探

3次阅读

共计 6146 个字符,预计需要花费 16 分钟才能阅读完成。

一、Promiz 是什么?

1、Promiz


  • A proper compact promise (promises/A+ spec compliant) library
  • A polyfill for ES6-style Promises in 913 bytes (gzip)

(一个体积很小的 promise 库)

2、Promise/A+ 标准是什么?


Promise/A+ 标准旨在为 Promise 提供一个可交互的 then 函数
标准呈现的起因

1、咱们不晓得异步申请什么时候返回数据,所以咱们就须要些回调函数。然而在某些状况下咱们须要晓得数据是在什么时候返回的,而后进行一些解决。
2、当咱们在异步回调外面解决的操作还是异步操作的时候,这样就造成了异步回调的嵌套
正是为了杜绝以上两种状况的呈现,社区呈现了 Promise/a+ 标准

标准的内容

1、不论进行什么操作都返回一个 promise 对象,这个对象外面会有一些属性和办法(这个成果相似于 jquery 中的链式编程,返回本人自身)
2、这个 promise 有三种状态

  • Unfulfilled(未实现,初始状态)
  • Fulfilled(已实现)
  • Failed(失败、回绝)

3、这个 promise 对象的应用时通过 then 办法进行的调用

3、Polyfill 是什么?


Polyfill 次要抚平不同浏览器之间对 js 实现的差别。比方,html5 的 storage(session,local), 不同浏览器,不同版本,有些反对,有些不反对。Polyfill 能够使不反对的浏览器反对 Storage(典型做法是在 IE 浏览器中减少 window.XMLHttpRequest,外部实现应用 ActiveXObject。)

polyfill 是一段代码(或者插件),提供了那些开发者们心愿浏览器原生提供反对的性能。程序库先查看浏览器是否反对某个 API,如果不反对则加载对应的 polyfill。次要特色:

  • 是一个浏览器 API 的 Shim
  • 与浏览器无关
  • 没有提供新的 API,只是在 API 中实现短少的性能
  • 只须要引入 polyfill,它会静静地工作

提到 Polyfill,不得不提 shim,polyfill 是 shim 的一种。

shim 的概念要比 polyfill 更大一些,能够将 polyfill 了解为专门兼容浏览器 API 的 shim。shim 是将不同 api 封装成一种,比方 jQuery 的 $.ajax 封装了 XMLHttpRequest 和 IE 用 ActiveXObject 形式创立 xhr 对象。它将一个新的 API 引入到一个旧的环境中, 而且仅靠旧环境中已有的伎俩实现。简略的说,如果浏览器 X 反对标准规定的性能,那么 polyfill 能够让浏览器 Y 的行为与浏览器 X 一样。

二、Promiz 怎么用?

bower install promiz --save
<!-- Browser -->
<script src='promiz.js'></script>

罕用 API:

  • new Promise(Function<resolve, reject>)
  • Promise.reject({reason})
  • Promise.resolve({value})
  • promise.then({Function}, {Function})
  • promise.catch({Function})
  • Promise.all({iterable})
  • Promise.race({iterable})

三、Promiz 源码解析

promiz.js 文件:

该文件蕴含一个立刻执行函数,其中包含构造函数 Deferred,最初将 Deferred 导出,作为 node.js 的库对象,或者作为全局范畴的变量。上面看下文件中代码:

if (!global.setImmediate)
    global.addEventListener('message', function (e) {if (e.source == global){if (isRunningTask)
          nextTick(queue[e.data])
        else {
          isRunningTask = true
          try {queue[e.data]()} catch (e) {}

          delete queue[e.data]
          isRunningTask = false
        }
      }
    })

以上代码监听 message 事件,执行队列中的异步函数,其中 nextTick 办法代码如下,次要兼容各种浏览器来实现异步执行函数

function nextTick(fn) {if (global.setImmediate) setImmediate(fn)
    // if inside of web worker  如果在 Web Worker 中应用以下办法
    else if (global.importScripts) setTimeout(fn)
    else {
      queueId++
      queue[queueId] = fn
      global.postMessage(queueId, '*')
    }
  }

以上代码次要是 promiz 通过 setImmediate、setTimeout 和 postMessage 三个办法来执行异步函数。如果要异步执行一个函数,咱们最先想到的办法必定会是 setTimeout,浏览器为了防止 setTimeout 嵌套可能呈现卡死 ui 线程的状况,为 setTimeout 设置了最小的执行工夫距离,不同浏览器的最小执行工夫距离都不一样。chrome 下测试 setTimeout 0 的理论执行工夫距离大略在 12ms 左右。

想最快地异步执行一个函数,能够应用 setImmediate 办法,该办法去实现比 setTimeout 0 更快的异步执行,执行工夫更靠近 0ms,然而只有 IE 浏览器反对。

除了应用异步函数外,还有一些办法能够实现异步调用。利用 onmessage:和 iframe 通信时经常会应用到 onmessage 办法,然而如果同一个 window postMessage 给本身,会怎么呢?其实也相当于异步执行了一个 function。

PostMessage 是 H5 中新增的办法,setTimeout 兼容性最佳,能够实用各种场景,因而在下面的代码中能够应用 setTimeout 做兜底,保障各种浏览器都能失常执行异步函数。

上面看构造函数的代码:

function Deferred(resolver) {
    'use strict'
    if (typeof resolver != 'function' && resolver != undefined)
      throw TypeError()

    if (typeof this != 'object' || (this && this.then))
      throw TypeError()
    
    // states
    // 0: pending
    // 1: resolving
    // 2: rejecting
    // 3: resolved
    // 4: rejected
    var self = this,
      state = 0,
      val = 0,
      next = [],
      fn, er;

    self['promise'] = self
    ...
   }

构造函数中首先存储 Promise 的状态(用 0 - 4 示意的五个状态)、Promise 的胜利值或者失败起因、下一个 Promise 的援用、Promise 的 then 办法中的胜利和失败回调函数。

 self['resolve'] = function (v) {
      fn = self.fn
      er = self.er
      if (!state) {
        val = v
        state = 1

        nextTick(fire)
      }
      return self
    }

    self['reject'] = function (v) {
      fn = self.fn
      er = self.er
      if (!state) {
        val = v
        state = 2

        nextTick(fire)

      }
      return self
    }

存储完数据后申明了 Promise 的 resolve 和 reject 函数,在两个函数外部都扭转了 state 的值,而后通过 nextTick 办法触发 fire 异步调用。

self['then'] = function (_fn, _er) {if (!(this._d == 1))
        throw TypeError()

      var d = new Deferred()

      d.fn = _fn
      d.er = _er
      if (state == 3) {d.resolve(val)
      }
      else if (state == 4) {d.reject(val)
      }
      else {next.push(d)
      }

      return d
    }

    self['catch'] = function (_er) {return self['then'](null, _er)
    }

申明了 Promise 的 then 和 catch 办法,在 then 办法通过判断 state 的值来确定以后 Promise 执行什么办法:如果 state 显示 Promise 变成 resolved 状态,那么立刻执行 resolve,如果 state 显示 Promise 变成 rejected 状态,那么立刻执行 reject,如果两者都不是,就把 then 办法的两个参数别离作为要返回的新的 Promise 的 resolve 和 reject 办法,并返回新的 Promise。Promise 的 catch 办法通过调用 then 办法,并将第一个参数设置为 null 实现,即 Promise 执行 resolve 后 catch 办法不进行解决,然而 Promise 执行 reject 后,调用传递进去的_er 办法对谬误进行解决。

上面介绍下 fire 办法:

 function fire() {

      // check if it's a thenable
      var ref;
      try {ref = val && val.then} catch (e) {
        val = e
        state = 2
        return fire()}

      thennable(ref, function () {
        state = 1
        fire()}, function () {
        state = 2
        fire()}, function () {
        try {if (state == 1 && typeof fn == 'function') {val = fn(val)
          }

          else if (state == 2 && typeof er == 'function') {val = er(val)
            state = 1
          }
        } catch (e) {
          val = e
          return finish()}

        if (val == self) {val = TypeError()
          finish()} else thennable(ref, function () {finish(3)
          }, finish, function () {finish(state == 1 && 3)
          })

      })
    }

从代码能够看出,fire 办法次要用来判断 ref 是否是一个 thenable 对象,而后调用了 thenable 函数,传递了 3 个回调函数。上面看一下 thennable 办法做了什么

// ref : reference to 'then' function 指向 thenable 对象的 `then` 函数
    // cb, ec, cn : successCallback, failureCallback, notThennableCallback
    function thennable (ref, cb, ec, cn) {if (state == 2) {return cn()
      }
      if ((typeof val == 'object' || typeof val == 'function') && typeof ref == 'function') {
        try {

          // cnt protects against abuse calls from spec checker
          var cnt = 0
          ref.call(val, function (v) {if (cnt++) return
            val = v
            cb()}, function (v) {if (cnt++) return
            val = v
            ec()})
        } catch (e) {
          val = e
          ec()}
      } else {cn()
      }
    };

在 thennable 办法中,首先判断,如果 ref 的 state 值是 2 也就是 Promise 的状态是 rejecting,就间接执行 cn 办法,间接传递 ref 的 reject 状态。当 ref 不是 thennable 对象时,也是间接执行 cn 办法。当 ref 的 state 值不是 2,且 ref 是 thennable 对象时,通过变量 cnt 来记录 ref 的状态,依据状态值来别离执行 cb 和 ec 办法,也就是别离执行 ref 的 resolve 办法和 reject 办法。

上面介绍 Deferred 的 API:

Deferred.all = function (arr) {if (!(this._d == 1))
      throw TypeError()

    if (!(arr instanceof Array))
      return Deferred.reject(TypeError())

    var d = new Deferred()

    function done(e, v) {if (v)
        return d.resolve(v)

      if (e)
        return d.reject(e)

      var unresolved = arr.reduce(function (cnt, v) {if (v && v.then)
          return cnt + 1
        return cnt
      }, 0)

      if(unresolved == 0)
        d.resolve(arr)

      arr.map(function (v, i) {if (v && v.then)
          v.then(function (r) {arr[i] = r
            done()
            return r
          }, done)
      })
    }

    done()

    return d
  }

代码实现了 Deferred 的 all 接口,该接口给数组的每个 Promise 都减少 then 办法,并通过 cnt 变量对的数组中 Promise 的 resolved 的数量进行计数,当全副量都变成 resolved 状态后,执行 resolve 办法。当其中有任何一个 Promise 变成 rejected 状态,执行 reject 办法。

Deferred.race = function (arr) {if (!(this._d == 1))
      throw TypeError()

    if (!(arr instanceof Array))
      return Deferred.reject(TypeError())

    if (arr.length == 0)
      return new Deferred()

    var d = new Deferred()

    function done(e, v) {if (v)
        return d.resolve(v)

      if (e)
        return d.reject(e)

      var unresolved = arr.reduce(function (cnt, v) {if (v && v.then)
          return cnt + 1
        return cnt
      }, 0)

      if(unresolved == 0)
        d.resolve(arr)

      arr.map(function (v, i) {if (v && v.then)
          v.then(function (r) {done(null, r)
          }, done)
      })
    }

    done()

    return d
  }

Promise 的 race 接口和 all 接口相似,也是通过给数组的每个 Promise 都减少 then 办法,并通过 cnt 变量对的数组中 Promise 的 resolved 的数量进行计数,不同的是 race 办法对将首先变成 resolved 状态的 Promise 进行 resolve。

四、总结

Promiz 的源码简练易读,次要蕴含一个构造函数用来创立 Promise 实例,实例实现了兼容性的执行异步函数,并定义了 Promise 的 resolve、reject、all、race 等接口,几百行代码解决了回调天堂的问题,构造和逻辑都很清晰。

正文完
 0