日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

方法 手写promise_JS探索-手写Promise

發布時間:2024/9/27 javascript 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 方法 手写promise_JS探索-手写Promise 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

無意間在知乎上刷到Monad這個概念,去了解了一下,前端的Promise就是一種Monad模式,所以試著學習一下手寫一個Promise.

本文內容主要參考于

只會用?一起來手寫一個合乎規范的Promise?www.jianshu.com

Promise是什么

所謂Promise,簡單說就是一個容器,里面保存著某個未來才會結束的事件(通常是一個異步操作)的結果。從語法上說,Promise 是一個對象,從它可以獲取異步操作的消息。Promise 提供統一的 API,各種異步操作都可以用同樣的方法進行處理。

Promise是處理異步編碼的一個解決方案,在Promise出現以前,異步代碼的編寫都是通過回調函數來處理的,回調函數本身沒有任何問題,只是當多次異步回調有邏輯關系時就會變得復雜:

const fs = require('fs'); fs.readFile('1.txt', (err,data) => {fs.readFile('2.txt', (err,data) => {fs.readFile('3.txt', (err,data) => {//可能還有后續代碼});}); });

上面讀取了3個文件,它們是層層遞進的關系,可以看到多個異步代碼套在一起不是縱向發展的,而是橫向,不論是從語法上還是從排錯上都不好,于是Promise的出現可以解決這一痛點。
上述代碼如果改寫成Promise版是這樣:

const util = require('util'); const fs = require('fs'); const readFile = util.promisify(fs.readFile);readFile('1.txt').then(data => {return readFile('2.txt');}).then(data => {return readFile('3.txt');}).then(data => {//...});

可以看到,代碼是從上至下縱向發展了,更加符合人們的邏輯。

下面手寫一個Promise,按照Promises/A+規范,可以參照規范原文:

Promises/A+?promisesaplus.com

手寫實現Promise是一道前端經典的面試題,比如美團的面試就是必考題,Promise的邏輯還是比較復雜的,考慮的邏輯也比較多,下面總結手寫Promise的關鍵點,和怎樣使用代碼來實現它。

Promise代碼基本結構

實例化Promise對象時傳入一個函數作為執行器,有兩個參數(resolve和reject)分別將結果變為成功態和失敗態。我們可以寫出基本結構

function Promise(executor) {this.state = 'pending'; //狀態this.value = undefined; //成功結果this.reason = undefined; //失敗原因function resolve(value) {}function reject(reason) {} }module.exports = Promise;

其中state屬性保存了Promise對象的狀態,規范中指明,一個Promise對象只有三種狀態:等待態(pending)成功態(resolved)和失敗態(rejected)。
當一個Promise對象執行成功了要有一個結果,它使用value屬性保存;也有可能由于某種原因失敗了,這個失敗原因放在reason屬性中保存。

then方法定義在原型上

每一個Promise實例都有一個then方法,它用來處理異步返回的結果,它是定義在原型上的方法,我們先寫一個空方法做好準備:

Promise.prototype.then = function (onFulfilled, onRejected) { };

當實例化Promise時會立即執行

當我們自己實例化一個Promise時,其執行器函數(executor)會立即執行,這是一定的:

let p = new Promise((resolve, reject) => {console.log('執行了'); }); //運行結果:執行了

因此,當實例化Promise時,構造函數中就要馬上調用傳入的executor函數執行

function Promise(executor) {var _this = this;this.state = 'pending';this.value = undefined;this.reason = undefined;executor(resolve, reject); //馬上執行function resolve(value) {}function reject(reason) {} }

已經是成功態或是失敗態不可再更新狀態

規范中規定,當Promise對象已經由pending狀態改變為了成功態(resolved)或是失敗態(rejected)就不能再次更改狀態了。因此我們在更新狀態時要判斷,如果當前狀態是pending(等待態)才可更新:

function resolve(value) {//當狀態為pending時再做更新if (_this.state === 'pending') {_this.value = value;//保存成功結果_this.state = 'resolved';}}function reject(reason) {//當狀態為pending時再做更新if (_this.state === 'pending') {_this.reason = reason;//保存失敗原因_this.state = 'rejected';}}

以上可以看到,在resolve和reject函數中分別加入了判斷,只有當前狀態是pending才可進行操作,同時將成功的結果和失敗的原因都保存到對應的屬性上。之后將state屬性置為更新后的狀態。

then方法的基本實現

當Promise的狀態發生了改變,不論是成功或是失敗都會調用then方法,所以,then方法的實現也很簡單,根據state狀態來調用不同的回調函數即可:

Promise.prototype.then = function (onFulfilled, onRejected) {if (this.state === 'resolved') {//判斷參數類型,是函數執行之if (typeof onFulfilled === 'function') {onFulfilled(this.value);}}if (this.state === 'rejected') {if (typeof onRejected === 'function') {onRejected(this.reason);}} };

需要一點注意,規范中說明了,onFulfilled 和 onRejected 都是可選參數,也就是說可以傳也可以不傳。傳入的回調函數也不是一個函數類型,那怎么辦?規范中說忽略它就好了。因此需要判斷一下回調函數的類型,如果明確是個函數再執行它。

讓Promise支持異步

代碼寫到這里似乎基本功能都實現了,可是還有一個很大的問題,目前此Promise還不支持異步代碼,如果Promise中封裝的是異步操作,then方法無能為力:

let p = new Promise((resolve, reject) => {setTimeout(() => {resolve(1);}, 500); });p.then(data => console.log(data)); //沒有任何結果

運行以上代碼發現沒有任何結果,本意是等500毫秒后執行then方法,哪里有問題呢?原因是setTimeout函數使得resolve是異步執行的,有延遲,當調用then方法的時候,此時此刻的狀態還是等待態(pending),因此then方法即沒有調用onFulfilled也沒有調用onRejected。
這個問題如何解決?我們可以參照發布訂閱模式,在執行then方法時如果還在等待態(pending),就把回調函數臨時寄存到一個數組里,當狀態發生改變時依次從數組中取出執行就好了,清楚這個思路我們實現它,首先在類上新增兩個Array類型的數組,用于存放回調函數:

function Promise(executor) {var _this = this;this.state = 'pending';this.value = undefined;this.reason = undefined;this.onFulfilledFunc = [];//保存成功回調this.onRejectedFunc = [];//保存失敗回調//其它代碼略... }

這樣當then方法執行時,若狀態還在等待態(pending),將回調函數依次放入數組中:

Promise.prototype.then = function (onFulfilled, onRejected) {//等待態,此時異步代碼還沒有走完if (this.state === 'pending') {if (typeof onFulfilled === 'function') {this.onFulfilledFunc.push(onFulfilled);//保存回調}if (typeof onRejected === 'function') {this.onRejectedFunc.push(onRejected);//保存回調}}//其它代碼略... };

寄存好了回調,接下來就是當狀態改變時執行就好了:

function resolve(value) {if (_this.state === 'pending') {_this.value = value;//依次執行成功回調_this.onFulfilledFunc.forEach(fn => fn(value));_this.state = 'resolved';}}function reject(reason) {if (_this.state === 'pending') {_this.reason = reason;//依次執行失敗回調_this.onRejectedFunc.forEach(fn => fn(reason));_this.state = 'rejected';}}

至此,Promise已經支持了異步操作,setTimeout延遲后也可正確執行then方法返回結果。

鏈式調用

Promise處理異步代碼最強大的地方就是支持鏈式調用,這塊也是最復雜的,我們先梳理一下規范中是怎么定義的:

  • 每個then方法都返回一個新的Promise對象(原理的核心)
  • 如果then方法中顯示地返回了一個Promise對象就以此對象為準,返回它的結果
  • 如果then方法中返回的是一個普通值(如Number、String等)就使用此值包裝成一個新的Promise對象返回。
  • 如果then方法中沒有return語句,就視為返回一個用Undefined包裝的Promise對象
  • 若then方法中出現異常,則調用失敗態方法(reject)跳轉到下一個then的onRejected
  • 如果then方法沒有傳入任何回調,則繼續向下傳遞(值的傳遞特性)。
  • 規范中說的很抽像,我們可以把不好理解的點使用代碼演示一下。
    其中第3項,如果返回是個普通值就使用它包裝成Promise,我們用代碼來演示:

    let p =new Promise((resolve,reject)=>{resolve(1); });p.then(data=>{return 2; //返回一個普通值 }).then(data=>{console.log(data); //輸出2 });

    可見,當then返回了一個普通的值時,下一個then的成功態回調中即可取到上一個then的返回結果,說明了上一個then正是使用2來包裝成的Promise,這符合規范中說的。
    第4項,如果then方法中沒有return語句,就視為返回一個用Undefined包裝的Promise對象

    let p = new Promise((resolve, reject) => {resolve(1); });p.then(data => {//沒有return語句 }).then(data => {console.log(data); //undefined });

    可以看到,當沒有返回任何值時不會報錯,沒有任何語句時實際上就是return undefined;即將undefined包裝成Promise對象傳給下一個then的成功態。
    第6項,如果then方法沒有傳入任何回調,則繼續向下傳遞,這是什么意思呢?這就是Promise中值的穿透,還是用代碼演示一下:

    let p = new Promise((resolve, reject) => {resolve(1); });p.then(data => 2) .then() .then() .then(data => {console.log(data); //2 });

    以上代碼,在第一個then方法之后連續調用了兩個空的then方法 ,沒有傳入任何回調函數,也沒有返回值,此時Promise會將值一值向下傳遞,直到你接收處理它,這就是所謂的值的穿透。
    現在可以明白鏈式調用的原理,不論是何種情況then方法都會返回一個Promise對象,這樣才會有下個then方法。
    搞清楚了這些點,我們就可以動手實現then方法的鏈式調用,一起來完善它:

    Promise.prototype.then = function (onFulfilled, onRejected) {var promise2 = new Promise((resolve, reject) => {//代碼略...})return promise2; };

    首先,不論何種情況then都返回Promise對象,我們就實例化一個新promise2并返回。
    接下來就處理根據上一個then方法的返回值來生成新Promise對象,由于這塊邏輯較復雜且有很多處調用,我們抽離出一個方法來操作,這也是規范中說明的:

    /*** 解析then返回值與新Promise對象* @param {Object} promise2 新的Promise對象 * @param {*} x 上一個then的返回值* @param {Function} resolve promise2的resolve* @param {Function} reject promise2的reject*/ function resolvePromise(promise2, x, resolve, reject) {//... }

    resolvePromise方法用來封裝鏈式調用產生的結果,下面我們分別一個個情況的寫出它的邏輯,首先規范中說明,如果promise2和 x 指向同一對象,就使用TypeError作為原因轉為失敗。原文如下:

    If promise and x refer to the same object, reject promise with a TypeError as the reason.

    這是什么意思?其實就是循環引用,當then的返回值與新生成的Promise對象為同一個(引用地址相同),則會拋出TypeError錯誤:

    let promise2 = p.then(data => {return promise2; });

    運行結果:

    TypeError: Chaining cycle detected for promise #<Promise>

    很顯然,如果返回了自己的Promise對象,狀態永遠為等待態(pending),再也無法成為resolved或是rejected,程序會死掉,因此首先要處理它:

    function resolvePromise(promise2, x, resolve, reject) {if (promise2 === x) {reject(new TypeError('Promise發生了循環引用'));} }

    接下來就是分各種情況處理。當x就是一個Promise,那么就執行它,成功即成功,失敗即失敗。若x是一個對象或是函數,再進一步處理它,否則就是一個普通值:

    function resolvePromise(promise2, x, resolve, reject) {if (promise2 === x) {reject(new TypeError('Promise發生了循環引用'));}if (x !== null && (typeof x === 'object' || typeof x === 'function')) {//可能是個對象或是函數} else {//否則是個普通值resolve(x);} }

    此時規范中說明,若是個對象,則嘗試將對象上的then方法取出來,此時如果報錯,那就將promise2轉為失敗態。原文:

    If retrieving the property x.then results in a thrown exception e, reject promise with e as the reason. //代碼略...if (x !== null && (typeof x === 'object' || typeof x === 'function')) {//可能是個對象或是函數try {let then = x.then;//取出then方法引用} catch (e) {reject(e);}} else {//否則是個普通值resolve(x);} }

    多說幾句,為什么取對象上的屬性有報錯的可能?Promise有很多實現(bluebird,Q等),Promises/A+只是一個規范,大家都按此規范來實現Promise才有可能通用,因此所有出錯的可能都要考慮到,假設另一個人實現的Promise對象使用Object.defineProperty()惡意的在取值時拋錯,我們可以防止代碼出現Bug。
    此時,如果對象中有then,且then是函數類型,就可以認為是一個Promise對象,之后,使用x作為this來調用then方法。

    //其他代碼略... if (x !== null && (typeof x === 'object' || typeof x === 'function')) {//可能是個對象或是函數try {let then = x.then; if (typeof then === 'function') {//then是function,那么執行Promisethen.call(x, (y) => {resolve(y);}, (r) => {reject(r);});} else {resolve(x);}} catch (e) {reject(e);}} else {//否則是個普通值resolve(x); }

    這樣鏈式寫法就基本完成了。但是還有一種極端的情況,如果Promise對象轉為成功態或是失敗時傳入的還是一個Promise對象,此時應該繼續執行,直到最后的Promise執行完。

    p.then(data => {return new Promise((resolve,reject)=>{//resolve傳入的還是Promiseresolve(new Promise((resolve,reject)=>{resolve(2);}));}); })

    此時就要使用遞歸操作了

    很簡單,把調用resolve改寫成遞歸執行resolvePromise方法即可,這樣直到解析Promise成一個普通值才會終止,即完成此規范:

    //其他代碼略... if (x !== null && (typeof x === 'object' || typeof x === 'function')) {//可能是個對象或是函數try {let then = x.then; if (typeof then === 'function') {let y = then.call(x, (y) => {//遞歸調用,傳入y若是Promise對象,繼續循環resolvePromise(promise2, y, resolve, reject);}, (r) => {reject(r);});} else {resolve(x);}} catch (e) {reject(e);}} else {//是個普通值,最終結束遞歸resolve(x); }

    到此,鏈式調用的代碼已全部完畢。在相應的地方調用resolvePromise方法即可。

    最后的最后

    其實,寫到此處Promise的真正源碼已經寫完了,但是距離100分還差一分,是什么呢?

    規范中說明,Promise的then方法是異步執行的。

    ES6的原生Promise對象已經實現了這一點,但是我們自己的代碼是同步執行,不相信可以試一下,那么如何將同步代碼變成異步執行呢?可以使用setTimeout函數來模擬一下:

    setTimeout(()=>{//此入的代碼會異步執行 },0);

    利用此技巧,將代碼then執行處的所有地方使用setTimeout變為異步即可,舉個栗子:

    setTimeout(() => {try {let x = onFulfilled(value);resolvePromise(promise2, x, resolve, reject);} catch (e) {reject(e);} },0);

    好了,現在已經是滿分的Promise源碼了。

    附上完整代碼

    function Promise(executor) { let self = this this.status = 'pending' //當前狀態 this.value = undefined //存儲成功的值 this.reason = undefined //存儲失敗的原因 this.onResolvedCallbacks = []//存儲成功的回調 this.onRejectedCallbacks = []//存儲失敗的回調 function resolve(value) {if (self.status == 'pending') {self.status = 'resolved'self.value = valueself.onResolvedCallbacks.forEach(fn => fn());} } function reject(error) {if (self.status == 'pending') {self.status = 'rejected'self.reason = errorself.onRejectedCallbacks.forEach(fn => fn())} } try {executor(resolve, reject) } catch (error) {reject(error) } } Promise.prototype.then = function (infulfilled, inrejected) { let self = this let promise2 infulfilled = typeof infulfilled === 'function' ? infulfilled : function (val) {return val } inrejected = typeof inrejected === 'function' ? inrejected : function (err) {throw err } if (this.status == 'resolved') {promise2 = new Promise(function (resolve, reject) {//x可能是一個promise,也可能是個普通值setTimeout(function () {try {let x = infulfilled(self.value)resolvePromise(promise2, x, resolve, reject)} catch (err) {reject(err)}});}) } if (this.status == 'rejected') {promise2 = new Promise(function (resolve, reject) {//x可能是一個promise,也可能是個普通值setTimeout(function () {try {let x = inrejected(self.reason)resolvePromise(promise2, x, resolve, reject)} catch (err) {reject(err)}});}) } if (this.status == 'pending') {promise2 = new Promise(function (resolve, reject) {self.onResolvedCallbacks.push(function () {//x可能是一個promise,也可能是個普通值setTimeout(function () {try {let x = infulfilled(self.value)resolvePromise(promise2, x, resolve, reject)} catch (err) {reject(err)}});})self.onRejectedCallbacks.push(function () {//x可能是一個promise,也可能是個普通值setTimeout(function () {try {let x = inrejected(self.reason)resolvePromise(promise2, x, resolve, reject)} catch (err) {reject(err)}});})}) } return promise2 } function resolvePromise(p2, x, resolve, reject) { if (p2 === x && x != undefined) {reject(new TypeError('類型錯誤')) } //可能是promise,看下對象中是否有then方法,如果有~那就是個promise if (x !== null && (typeof x === 'object' || typeof x === 'function')) {try {//為了防止出現 {then:11}這種情況,需要判斷then是不是一個函數let then = x.thenif (typeof then === 'function') {then.call(x, function (y) {//y 可能還是一個promise,那就再去解析,知道返回一個普通值為止resolvePromise(p2, y, resolve, reject)}, function (err) {reject(err)})} else {//如果then不是function 那可能是對象或常量resolve(x)}} catch (e) {reject(e)} } else {//說明是一個普通值resolve(x) } }

    總結

    以上是生活随笔為你收集整理的方法 手写promise_JS探索-手写Promise的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: www.久久国产 | 98视频在线 | 777精品视频 | 诱夫1v1高h| 欧美中文| 亚洲AV永久无码国产精品国产 | 成人欧美视频 | 水蜜桃色314在线观看 | 加勒比不卡视频 | 国产欲妇 | 欧美成人免费视频 | 激情开心站 | 可以在线看黄的网站 | 精品视频在线观看 | 国产一区二区三区在线观看 | 特种兵之深入敌后高清全集免费观看 | 国产porn| av免费在线观看不卡 | 亚洲最大成人av | 国产欧美精品一区二区色综合朱莉 | 1024手机看片国产 | 青青草原成人网 | 亚洲激情av | 奇米影视大全 | 污片网站| 日韩网站免费观看高清 | 特黄一级大片 | 欧美日韩亚洲高清 | 超碰v| 香蕉黄色片 | 免费看黄在线看 | 欧美日韩激情 | jizz性欧美15| 日韩午夜高清 | 丝袜美腿中文字幕 | 欧美夫妻性生活视频 | 快色在线观看 | 天堂中文字幕在线 | 亚洲精品国产精品乱码在线观看 | 色综合综合网 | 国产精品一二三四五区 | xxxx毛片 | 国产有码在线观看 | 狠狠综合久久av一区二区 | 大黄毛片 | 修仙淫交(高h)h文 | 草久在线| av制服丝袜 | 日韩视频网站在线观看 | 韩国一区二区三区四区 | 国产婷婷一区二区 | 欧美极品第一页 | 这里只有精品视频在线观看 | 最近高清中文在线字幕在线观看 | 岛国片在线播放 | 亚洲a视频 | 青娱乐最新地址 | 97久久久 | 免费在线日本 | 夫妻黄色片 | 欧美一区二区三区在线免费观看 | 九色porny视频 | 欧美日本一道本 | 亚洲精品国产精品国自产观看浪潮 | 久久久久久久久久久久久国产 | 亚洲va国产天堂va久久 en | 无码人妻精品一区二区三区夜夜嗨 | 成人h动漫精品一区 | 亚洲va天堂va国产va久 | 国产精品第四页 | av网站在线播放 | 爱情岛论坛亚洲入口 | 天堂√8在线中文 | www成人免费视频 | 黄色网av | 91久久国产综合久久91精品网站 | 日本乱轮视频 | 欧美日韩视频一区二区三区 | 国产精品人| 国产福利视频在线观看 | 中文字幕人妻一区 | 伊人网综合| 手机在线播放av | 高h在线观看 | 日韩精品成人在线 | 久久精品韩国 | 日韩人妻精品一区二区三区视频 | 狠狠欧美 | 欧美一级片一区 | 中文字幕25页 | 日本一区二区不卡视频 | 中文欧美日韩 | 超碰91人人 | 久久久久久久久久久久久久av | 久久草视频 | 日韩美一区二区 | 亚洲精品乱码久久久久久久久久久久 | 丁香婷婷社区 | 亚洲国产成人精品视频 |