摘要:在和方法執(zhí)行的時候訂閱事件,將自己的回調(diào)函數(shù)綁定到事件上,屬性是發(fā)布者,一旦它的值發(fā)生改變就發(fā)布事件,執(zhí)行回調(diào)函數(shù)。實現(xiàn)和方法的回調(diào)函數(shù)都是,當滿足條件對象狀態(tài)改變時,這些回調(diào)會被放入隊列。所以我需要在某個變?yōu)闀r,刪除它們綁定的回調(diào)函數(shù)。
前言
按照文檔說明簡單地實現(xiàn) ES6 Promise的各個方法并不難,但是Promise的一些特殊需求實現(xiàn)起來并不簡單,我首先提出一些不好實現(xiàn)或者容易忽略的需求:
數(shù)據(jù)傳遞
回調(diào)綁定
將回調(diào)變成 microtask
實現(xiàn) then/finally 返回的pending promise “跟隨”它們的回調(diào)返回的pending promise
實現(xiàn) resolve 返回的 promise “跟隨”它的thenable對象參數(shù)
實現(xiàn)框架在解決上述問題前,我們先實現(xiàn)一個框架。
首先,我的目的是實現(xiàn)一個Promise插件,它包括:
構(gòu)造函數(shù):Promise
靜態(tài)方法:resolve、reject、all 和 race
實例方法:then、catch 和 finally
私有函數(shù):identity、thrower 和 isSettled 等
如下:
;(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; })();解決問題
接下來,我們解決各個問題。
數(shù)據(jù)傳遞為了傳遞數(shù)據(jù)——回調(diào)函數(shù)需要用到的參數(shù)以及 promise 的狀態(tài),我們首先在構(gòu)造函數(shù)Promise中給新生成的對象添加status、value和reason屬性,并且在構(gòu)造函數(shù)中執(zhí)行 executor 函數(shù):
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 對象的引用)來訪問這些數(shù)據(jù),并將其用作回調(diào)函數(shù)的參數(shù)。
按照文檔說明,為了實現(xiàn)鏈式調(diào)用,Promise的所有方法都會返回一個 Promise 對象,而且除了Promise.resolve(peomiseObj) 這種情況外都是新生成的 Promise 對象。所以接下來我的大部分方法都會返回一個新的 promise 對象。不生成新對象的特例:
var a = Promise.resolve("a"), b = Promise.resolve(a); console.log(a === b) //true回調(diào)綁定
接下來,我們要將then、catch和finally中的回調(diào)方法綁定到Promise對象的狀態(tài)改變這個事件上。
我想到的第一個事件就是onchange事件,但是 promiseObj.status 屬性上并沒有change事件。但是,我馬上想到每次設(shè)置accessor屬性的值時,就會調(diào)用 accessor 屬性的setter方法。那么,我只要把status屬性設(shè)置為存取屬性,然后在它的 setter 方法里觸發(fā)綁定的回調(diào)函數(shù)就行啦!如下:
function Promise(executor) { var self = this; //存儲狀態(tài)的私有屬性 this._status = "pending"; this.value = undefined; this.reason = undefined; //this.events = new Events(); //存儲狀態(tài)的公開屬性 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; }
為了綁定回調(diào)函數(shù),我使用了發(fā)布訂閱模式。在then、catch和finally方法執(zhí)行的時候訂閱事件change,將自己的回調(diào)函數(shù)綁定到change事件上,promiseObj.status 屬性是發(fā)布者,一旦它的值發(fā)生改變就發(fā)布change事件,執(zhí)行回調(diào)函數(shù)。
為了節(jié)省篇幅,不那么重要的發(fā)布者Events() 構(gòu)造函數(shù)及其原型我就不貼代碼了,文章末尾我會給出源代碼。
then、catch和finally方法的回調(diào)函數(shù)都是microtask,當滿足條件(promise 對象狀態(tài)改變)時,這些回調(diào)會被放入microtask隊列。每當調(diào)用棧中的macrotask執(zhí)行完畢時,立刻執(zhí)行microtask隊列中所有的microtask,這樣一次事件循環(huán)就結(jié)束了,js引擎等待下一次循環(huán)。
要我實現(xiàn)microtask我是做不到的,我就只能用macrotask模仿一下microtask了。
我用 setTimeout 發(fā)布的macrotask進行模仿:
Object.defineProperty(this, "status", { get: function() { return self._status; }, set: function(newValue) { self._status = newValue; setTimeout(() => { self.events.fireEvent("change"); }, 0); }, configurable: true });實現(xiàn)函數(shù)
接下來,我們實現(xiàn)各個函數(shù)和方法。在知道方法的參數(shù)和返回值后再實現(xiàn)方法如有神助,而實現(xiàn)過程中最難處理的就是 pending 狀態(tài)的 promise 對象,因為我們要等它變成其它狀態(tài)時,再做真正的處理。下面我拿出兩個最具代表性的方法來分析。
靜態(tài)方法all如果忘記了 Promise.all(iterable) 的參數(shù)和返回值,可以返回我上一篇文章查看。
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 的狀態(tài) 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 對象都變?yōu)?fulfilled 狀態(tài),或者 iterable 對象內(nèi)沒有 promise 對象, //由于我們可能需要等待 pending promise 的結(jié)果,所以要額外花費一個變量計數(shù),而不能用valueArr的長度判斷。 if (count === len) { pro.value = valueArr; pro.status = "fulfilled"; } }, 0); return pro; }
這里解釋兩點:
1、如何保證 value 數(shù)組中值的順序
如果iterable對象中的 promise 對象都變?yōu)?fulfilled 狀態(tài),或者 iterable 對象內(nèi)沒有 promise 對象,all 返回一個 fulfilled promise 對象,且其 value 值為 iterable 中各項值組成的數(shù)組,數(shù)組中的值將會按照 iterable 內(nèi)的順序排列,而不是由 pending promise 的完成順序決定。
為了保證 value 數(shù)組中值的順序,最簡單的方法是
valueArr[iterable.indexOf(val)] = val.value;
但是像除 Array、TypedArray 和 String 外的 Map 和 Set 原生 iterabe 對象,以及其它通過myIterable[Symbol.iterator] 創(chuàng)建的自定義的 iterable 對象都沒有 indexOf 方法,所以我選擇用閉包來保證 value 數(shù)組值的順序。
2、處理 pending promise 對象。
pending promise 是導致這個函數(shù)要額外添加很多變量存儲狀態(tài),額外做很多判斷和處理的罪魁禍首。
如果 iterabe 對象中有一個pending狀態(tài)的 promise(通常為一個異步的 promise),我們就使用then方法來持續(xù)關(guān)注它的動態(tài)。
一旦它變成fulfilledpromise,就將它的 value 加入 valueArr 數(shù)組。我們添加一個 count 變量記錄目前 valueArr 獲取到了多少個值,當全部獲取到值后,就可以給 pro.value 和pro.status 賦值了。之所以用 count 而不是 valueArr.length 判斷,是因為 valueArr = [undefined,undefined,undefined,1] 的長度也為4,這樣可能導致還沒獲取到 pending promise 的值就改變 pro.status 了。
而當它變成rejectedpromise 時,我們就更新 all 方法返回的對象的 reason 值,同時改變狀態(tài) status 為 rejected,觸發(fā)綁定的onrejected函數(shù)。另外,為了與原生 Promise 表現(xiàn)相同:如果 iterable 對象中任意一個 pending promise 對象狀態(tài)變?yōu)?rejected,將不再持續(xù)關(guān)注其它 pending promise 的動態(tài)。而我早就在所有的 pending promise 上都綁定了 onfulfilled 和 onrejected 函數(shù),用來跟蹤它們。所以我需要在某個 pending promise 變?yōu)?rejected promise 時,刪除它們綁定的回調(diào)函數(shù)。
實例方法thenPromise.prototype.then(onFulfilled, onRejected):
Promise.prototype.then = function(onFulfilled, onRejected) { var pro = new Promise(); //綁定回調(diào)函數(shù),onFulfilled 和 onRejected 用一個回調(diào)函數(shù)處理 this.events.addEvent("change", hander.bind(null, this)); function hander(that) { var res; //onFulfilled 或 onRejected 回調(diào)函數(shù)執(zhí)行后得到的結(jié)果 try { if (that.status === "fulfilled") { //如果onFulfilled不是函數(shù),它會在then方法內(nèi)部被替換成一個 Identity 函數(shù) typeof onFulfilled !== "function" ? onFulfilled = identity: false; //將參數(shù) this.value 傳入 onFulfilled 并執(zhí)行,將結(jié)果賦給 res res = onFulfilled.call(null, that.value); } else if (that.status === "rejected") { //如果onRejected不是函數(shù),它會在then方法內(nèi)部被替換成一個 Thrower 函數(shù) 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 { //回調(diào)函數(shù)返回一個值或不返回任何內(nèi)容,情況1、2 pro.value = res; pro.status = "fulfilled"; } } return pro; };
我想我已經(jīng)注釋得很清楚了,可以對照我上一篇文章進行閱讀。
我再說明一下pending promise 的“跟隨”情況,和 all 方法的實現(xiàn)方式差不多,這里也是用 res.then來“跟隨”的。我相信大家都看得懂代碼,下面我舉個例子來實踐一下:
var fromCallback; var fromThen = Promise.resolve("done") .then(function onFulfilled(value) { fromCallback = new Promise(function(resolve){ setTimeout(() => resolve(value), 0); //未執(zhí)行 setTimeout 的回調(diào)方法之前 fromCallback 為"pending"狀態(tài) }); return fromCallback; //then 方法返回的 fromThen 將跟隨 onFulfilled 方法返回的 fromCallback }); setTimeout(function() { //目前已執(zhí)行完 onFulfilled 回調(diào)函數(shù),fromCallback 為"pending"狀態(tài),fromThen ‘跟隨’ fromCallback console.log(fromCallback.status); //fromCallback.status === "pending" console.log(fromThen.status); //fromThen.status === "pending" setTimeout(function() { //目前已執(zhí)行完 setTimeout 中的回調(diào)函數(shù),fromCallback 為"fulfilled"狀態(tài),fromThen 也跟著變?yōu)?fulfilled"狀態(tài) 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 參數(shù)改為 0 試試 }, 0);
看完這個例子,我相信大家都搞懂了then的回調(diào)函數(shù)返回 pending promise 時它會怎么處理了。
另外,這個例子也體現(xiàn)出我用 setTimeout 分發(fā)的macrotask模擬microtask的不足之處了,如果將倒數(shù)第二行的的 delay 參數(shù)改為 0,那么 fromThen.status === "pending",這說明修改它狀態(tài)的代碼在 log 它狀態(tài)的代碼之后執(zhí)行,至于原因大家自己想一下,這涉及到 event loop。
各位大俠請點下面的鏈接進行測試:
https://codepen.io/lyl123321/...
或者直接點這里查看源代碼:
https://github.com/lyl123321/...
新增 Promise.try:
https://github.com/lyl123321/...
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/102611.html
摘要:意味著操作成功完成。方法接收失敗情況的回調(diào)函數(shù)作為參數(shù),返回一個對象。參數(shù)回調(diào)函數(shù)不接收任何參數(shù),當對象變成狀態(tài)時被調(diào)用?,F(xiàn)在各個方法的參數(shù)返回值功能和使用方法已經(jīng)有個大概的了解了,為了進一步理解其原理,接下來我打算簡單地實現(xiàn)一下它。 前言 最近幾周參加筆試面試,總是會遇到實現(xiàn)異步和處理異步的問題,然而作者每次都無法完美地回答。在最近一次筆試因為 Promise 而被刷掉后,我終于下定...
摘要:從最開始的到封裝后的都在試圖解決異步編程過程中的問題。為了讓編程更美好,我們就需要引入來降低異步編程的復雜性。寫一個符合規(guī)范并可配合使用的寫一個符合規(guī)范并可配合使用的理解的工作原理采用回調(diào)函數(shù)來處理異步編程。 JavaScript怎么使用循環(huán)代替(異步)遞歸 問題描述 在開發(fā)過程中,遇到一個需求:在系統(tǒng)初始化時通過http獲取一個第三方服務(wù)器端的列表,第三方服務(wù)器提供了一個接口,可通過...
摘要:當前的部分代碼狀態(tài)超時再縮小了范圍以后,進一步進行排查。函數(shù)是一個很簡單的一次性函數(shù),在第一次被觸發(fā)時調(diào)用函數(shù)。因為上述使用的是,而非,所以在獲取的時候,肯定為空,那么這就意味著會繼續(xù)調(diào)用函數(shù)。 有時候,所見并不是所得,有些包,你需要去翻他的源碼才知道為什么會這樣。 背景 今天調(diào)試一個程序,用到了一個很久之前的NPM包,名為formstream,用來將form表單數(shù)據(jù)轉(zhuǎn)換為流的形式進行...
摘要:理解承諾有兩個部分。如果異步操作成功,則通過的創(chuàng)建者調(diào)用函數(shù)返回預期結(jié)果,同樣,如果出現(xiàn)意外錯誤,則通過調(diào)用函數(shù)傳遞錯誤具體信息。這將與理解對象密切相關(guān)。這個函數(shù)將創(chuàng)建一個,該將在到秒之間的隨機數(shù)秒后執(zhí)行或。 想閱讀更多優(yōu)質(zhì)文章請猛戳GitHub博客,一年百來篇優(yōu)質(zhì)文章等著你! showImg(https://segmentfault.com/img/bVbkNvF?w=1280&h=...
摘要:理解承諾有兩個部分。如果異步操作成功,則通過的創(chuàng)建者調(diào)用函數(shù)返回預期結(jié)果,同樣,如果出現(xiàn)意外錯誤,則通過調(diào)用函數(shù)傳遞錯誤具體信息。這將與理解對象密切相關(guān)。這個函數(shù)將創(chuàng)建一個,該將在到秒之間的隨機數(shù)秒后執(zhí)行或。 想閱讀更多優(yōu)質(zhì)文章請猛戳GitHub博客,一年百來篇優(yōu)質(zhì)文章等著你! showImg(https://segmentfault.com/img/bVbkNvF?w=1280&h=...
閱讀 3751·2023-04-26 02:32
閱讀 4244·2021-11-23 10:05
閱讀 2377·2021-10-08 10:04
閱讀 2874·2021-09-22 16:06
閱讀 3695·2021-09-22 15:27
閱讀 828·2019-08-30 15:54
閱讀 1862·2019-08-30 13:50
閱讀 2773·2019-08-29 13:56