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