当前位置:网站首页>Promiz初探

Promiz初探

2020-11-10 11:24:00 涛八八

一、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方法进行的调用
image.png

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等接口,几百行代码解决了回调地狱的问题,结构和逻辑都很清晰。

版权声明
本文为[涛八八]所创,转载请带上原文链接,感谢
https://segmentfault.com/a/1190000037783162