按照文檔說明簡單地實現 ES6 Promise
的各個方法並不難,可是Promise
的一些特殊需求實現起來並不簡單,我首先提出一些很差實現或者容易忽略的需求:git
pending
promise 「跟隨」它們的回調返回的pending
promisethenable
對象參數在解決上述問題前,咱們先實現一個框架。
首先,個人目的是實現一個Promise
插件,它包括:es6
以下:github
;(function() { function Promise(executor) { } Object.defineProperties(Promise, { resolve: { value: resolve, configurable: true, writable: true }, reject: { value: reject, configurable: true, writable: true }, race: { value: race, configurable: true, writable: true }, all: { value: all, configurable: true, writable: true } }); Promise.prototype = { constructor: Promise, then: function(onFulfilled, onRejected) { }, catch: function(onRejected) { }, finally: function(onFinally) { }, } function identity(value) { return value; } function thrower(reason) { throw reason; } function isSettled(pro) { return pro instanceof Promise ? pro.status === 'fulfilled' || pro.status === 'rejected' : false; } window.Promise = Promise; })();
接下來,咱們解決各個問題。數組
爲了傳遞數據——回調函數須要用到的參數以及 promise 的狀態,咱們首先在構造函數Promise
中給新生成的對象添加status
、value
和reason
屬性,而且在構造函數中執行 executor
函數:promise
function Promise(executor) { var self = this; this.status = 'pending'; this.value = undefined; this.reason = undefined; typeof executor === 'function' ? executor.call(null, function(value) { self.value = value; self.status = 'fulfilled'; }, function(reason) { self.reason = reason; self.status = 'rejected'; }) : false; }
咱們將 value、reason 和 status 保存在 Promise 對象中,這樣,咱們就能夠在 Promise 對象的方法中經過this
(即 Promise 對象的引用)來訪問這些數據,並將其用做回調函數的參數。閉包
按照文檔說明,爲了實現鏈式調用,Promise
的全部方法都會返回一個 Promise 對象,並且除了Promise.resolve(peomiseObj) 這種狀況外都是新生成的 Promise 對象。因此接下來個人大部分方法都會返回一個新的 promise 對象。不生成新對象的特例:框架
var a = Promise.resolve('a'), b = Promise.resolve(a); console.log(a === b) //true
接下來,咱們要將then
、catch
和finally
中的回調方法綁定到Promise
對象的狀態改變這個事件上。
我想到的第一個事件就是onchange
事件,可是 promiseObj.status 屬性上並無change
事件。可是,我立刻想到每次設置accessor
屬性的值時,就會調用 accessor 屬性的setter
方法。那麼,我只要把status
屬性設置爲存取屬性,而後在它的 setter 方法裏觸發綁定的回調函數就行啦!以下:less
function Promise(executor) { var self = this; //存儲狀態的私有屬性 this._status = 'pending'; this.value = undefined; this.reason = undefined; //this.events = new Events(); //存儲狀態的公開屬性 Object.defineProperty(this, 'status', { get: function() { return self._status; }, set: function(newValue) { self._status = newValue; //self.events.fireEvent('change'); }, configurable: true }); typeof executor === 'function' ? executor.call(null, function(value) { self.value = value; self.status = 'fulfilled'; }, function(reason) { self.reason = reason; self.status = 'rejected'; }) : false; }
爲了綁定回調函數,我使用了發佈訂閱模式。在then
、catch
和finally
方法執行的時候訂閱事件change
,將本身的回調函數綁定到change
事件上,promiseObj.status 屬性是發佈者,一旦它的值發生改變就發佈change
事件,執行回調函數。
爲了節省篇幅,不那麼重要的發佈者Events
() 構造函數及其原型我就不貼代碼了,文章末尾我會給出源代碼。異步
then
、catch
和finally
方法的回調函數都是microtask
,當知足條件(promise 對象狀態改變)時,這些回調會被放入microtask
隊列。每當調用棧中的macrotask
執行完畢時,馬上執行microtask
隊列中全部的microtask
,這樣一次事件循環就結束了,js引擎等待下一次循環。
要我實現microtask
我是作不到的,我就只能用macrotask
模仿一下microtask
了。
我用 setTimeout 發佈的macrotask
進行模仿:ide
Object.defineProperty(this, 'status', { get: function() { return self._status; }, set: function(newValue) { self._status = newValue; setTimeout(() => { self.events.fireEvent('change'); }, 0); }, configurable: true });
接下來,咱們實現各個函數和方法。在知道方法的參數和返回值後再實現方法若有神助,而實現過程當中最難處理的就是 pending 狀態的 promise 對象,由於咱們要等它變成其它狀態時,再作真正的處理。下面我拿出兩個最具表明性的方法來分析。
all
若是忘記了 Promise.all
(iterable) 的參數和返回值,能夠返回我上一篇文章查看。
function all(iterable) { //若是 iterable 不是一個可迭代對象 if (iterable[Symbol.iterator] == undefined) { let err = new TypeError(typeof iterable + iterable + ' is not iterable (cannot read property Symbol(Symbol.iterator))'); return Promise.reject(err); } //若是 iterable 對象爲空 if (iterable.length === 0) { return Promise.resolve([]); } //其它狀況用異步處理 var pro = new Promise(), //all 返回的 promise 對象 valueArr = []; //all 返回的 promise 對象的 value 屬性 setTimeout(function() { var index = 0, //記錄當前索引 count = 0, len = iterable.length; for (let val of iterable) { - function(i) { if (val instanceof Promise) { //當前值爲 Promise 對象時 if (val.status === 'pending') { val.then(function(value) { valueArr[i] = value; count++; //Promise.all([new Promise(function(resolve){setTimeout(resolve, 100, 1)}), 2, 3, 4]) if (count === len) { pro.value = valueArr; pro.status = 'fulfilled'; } }, function(reason) { pro.reason = reason; pro.status = 'rejected'; //當一個pending Promise首先完成時,解除其它 pending Promise的事件,防止以後其它 Promise 改變 pro 的狀態 for (let uselessPromise of iterable) { if (uselessPromise instanceof Promise && uselessPromise.status === 'pending') { uselessPromise.events.removeEvent('change'); } } }); } else if (val.status === 'rejected') { pro.reason = val.reason; pro.status = 'rejected'; return; } else { //val.status === 'fulfilled' valueArr[i] = val.value; count++; } } else { valueArr[i] = val; count++; } index++; } (index); } //若是 iterable 對象中的 promise 對象都變爲 fulfilled 狀態,或者 iterable 對象內沒有 promise 對象, //因爲咱們可能須要等待 pending promise 的結果,因此要額外花費一個變量計數,而不能用valueArr的長度判斷。 if (count === len) { pro.value = valueArr; pro.status = 'fulfilled'; } }, 0); return pro; }
這裏解釋兩點:
一、如何保證 value 數組中值的順序
若是iterable對象中的 promise 對象都變爲 fulfilled 狀態,或者 iterable 對象內沒有 promise 對象,all 返回一個 fulfilled promise 對象,且其 value 值爲 iterable 中各項值組成的數組,數組中的值將會按照 iterable 內的順序排列,而不是由 pending promise 的完成順序決定。
爲了保證 value 數組中值的順序,最簡單的方法是
valueArr[iterable.indexOf(val)] = val.value;
可是像除 Array、TypedArray 和 String 外的 Map 和 Set 原生 iterabe 對象,以及其它經過myIterable[Symbol.iterator] 建立的自定義的 iterable 對象都沒有 indexOf 方法,因此我選擇用閉包來保證 value 數組值的順序。
二、處理 pending promise 對象。
pending promise 是致使這個函數要額外添加不少變量存儲狀態,額外作不少判斷和處理的罪魁禍首。
若是 iterabe 對象中有一個pending
狀態的 promise(一般爲一個異步的 promise),咱們就使用then
方法來持續關注它的動態。
fulfilled
promise,就將它的 value 加入 valueArr 數組。咱們添加一個 count 變量記錄目前 valueArr 獲取到了多少個值,當所有獲取到值後,就能夠給 pro.value 和pro.status 賦值了。之因此用 count 而不是 valueArr.length 判斷,是由於 valueArr = [undefined,undefined,undefined,1] 的長度也爲4,這樣可能致使還沒獲取到 pending promise 的值就改變 pro.status 了。rejected
promise 時,咱們就更新 all 方法返回的對象的 reason 值,同時改變狀態 status 爲 rejected,觸發綁定的onrejected
函數。另外,爲了與原生 Promise 表現相同:若是 iterable 對象中任意一個 pending promise 對象狀態變爲 rejected
,將再也不持續關注其它 pending promise 的動態。而我早就在全部的 pending promise 上都綁定了 onfulfilled 和 onrejected 函數,用來跟蹤它們。因此我須要在某個 pending promise 變爲 rejected promise 時,刪除它們綁定的回調函數。then
Promise.prototype.then
(onFulfilled, onRejected):
Promise.prototype.then = function(onFulfilled, onRejected) { var pro = new Promise(); //綁定回調函數,onFulfilled 和 onRejected 用一個回調函數處理 this.events.addEvent('change', hander.bind(null, this)); function hander(that) { var res; //onFulfilled 或 onRejected 回調函數執行後獲得的結果 try { if (that.status === 'fulfilled') { //若是onFulfilled不是函數,它會在then方法內部被替換成一個 Identity 函數 typeof onFulfilled !== 'function' ? onFulfilled = identity: false; //將參數 this.value 傳入 onFulfilled 並執行,將結果賦給 res res = onFulfilled.call(null, that.value); } else if (that.status === 'rejected') { //若是onRejected不是函數,它會在then方法內部被替換成一個 Thrower 函數 typeof onRejected !== 'function' ? onRejected = thrower: false; res = onRejected.call(null, that.reason); } } catch(err) { //拋出一個錯誤,狀況3 pro.reason = err; pro.status = 'rejected'; return; } if (res instanceof Promise) { if (res.status === 'fulfilled') { //狀況4 pro.value = res.value; pro.status = 'fulfilled'; } else if (res.status === 'rejected') { //狀況5 pro.reason = res.reason; pro.status = 'rejected'; } else { //狀況6 //res.status === 'pending'時,pro 跟隨 res pro.status = 'pending'; res.then(function(value) { pro.value = value; pro.status = 'fulfilled'; }, function(reason) { pro.reason = reason; pro.status = 'rejected'; }); } } else { //回調函數返回一個值或不返回任何內容,狀況一、2 pro.value = res; pro.status = 'fulfilled'; } } return pro; };
我想我已經註釋得很清楚了,能夠對照我上一篇文章進行閱讀。
我再說明一下pending promise 的「跟隨」狀況,和 all 方法的實現方式差很少,這裏也是用 res.then
來「跟隨」的。我相信你們都看得懂代碼,下面我舉個例子來實踐一下:
var fromCallback; var fromThen = Promise.resolve('done') .then(function onFulfilled(value) { fromCallback = new Promise(function(resolve){ setTimeout(() => resolve(value), 0); //未執行 setTimeout 的回調方法以前 fromCallback 爲'pending'狀態 }); return fromCallback; //then 方法返回的 fromThen 將跟隨 onFulfilled 方法返回的 fromCallback }); setTimeout(function() { //目前已執行完 onFulfilled 回調函數,fromCallback 爲'pending'狀態,fromThen ‘跟隨’ fromCallback console.log(fromCallback.status); //fromCallback.status === 'pending' console.log(fromThen.status); //fromThen.status === 'pending' setTimeout(function() { //目前已執行完 setTimeout 中的回調函數,fromCallback 爲'fulfilled'狀態,fromThen 也跟着變爲'fulfilled'狀態 console.log(fromCallback.status + ' ' + fromCallback.value); //fromCallback.status === 'fulfilled' console.log(fromThen.status + ' ' + fromThen.value); //fromThen.status === 'fulfilled' console.log(fromCallback === fromThen); //false }, 10); //將這個 delay 參數改成 0 試試 }, 0);
看完這個例子,我相信你們都搞懂了then
的回調函數返回 pending promise 時它會怎麼處理了。
另外,這個例子也體現出我用 setTimeout 分發的macrotask
模擬microtask
的不足之處了,若是將倒數第二行的的 delay 參數改成 0,那麼 fromThen.status === 'pending',這說明修改它狀態的代碼在 log 它狀態的代碼以後執行,至於緣由你們本身想一下,這涉及到 event loop。
各位大俠請點下面的連接進行測試:
https://codepen.io/lyl123321/...
或者直接點這裏查看源代碼:
https://github.com/lyl123321/...
新增 Promise.try:
https://github.com/lyl123321/...