詳解JavaScript如何實(shí)現(xiàn)一個(gè)簡(jiǎn)易的Promise對(duì)象
前言
實(shí)現(xiàn)一個(gè)簡(jiǎn)易的Promise對(duì)象,我們首先要了解幾個(gè)相關(guān)的知識(shí)點(diǎn):
Promise對(duì)象的狀態(tài): pending(進(jìn)行中)、fulfilled(已成功)和rejected(已失?。?。只有異步操作的結(jié)果,可以決定當(dāng)前是哪一種狀態(tài),任何其他操作都無(wú)法改變這個(gè)狀態(tài)。這也是Promise這個(gè)名字的由來,它的英語(yǔ)意思就是“承諾”,表示其他手段無(wú)法改變。
Promise的參數(shù): Promise構(gòu)造函數(shù)接收一個(gè)函數(shù)作為參數(shù),函數(shù)內(nèi)部有兩個(gè)參數(shù),分別是resolve和reject,這兩個(gè)參數(shù)是兩個(gè)函數(shù),由JS引擎提供,不需要我們部署。reslove函數(shù)
的作用是將Promise對(duì)象的狀態(tài)由 'pending' 狀態(tài)變?yōu)?'resolved'狀態(tài)即('fulfilled'狀態(tài)),方便與參數(shù)名對(duì)應(yīng),reject函數(shù)
的作用是將Promise對(duì)象的狀態(tài)由 'pending' 狀態(tài)變?yōu)?'rejected'狀態(tài)。
但是我們應(yīng)該注意的是,Promise對(duì)象的狀態(tài)一經(jīng)改變,就不再發(fā)生改變(即pending
--> resolved
|| pending
--> rejected
其中任意一種發(fā)生改變之后,Promise對(duì)象的狀態(tài)將不再發(fā)生改變)
Promise的基礎(chǔ)結(jié)構(gòu)與用法
let p1 = new Promise((resolve, reject) => { resolve('成功'); reject('失敗'); throw('報(bào)錯(cuò)'); //相當(dāng)于reject() }) console.log(p1);
讓我們看看三種狀態(tài)的打印的結(jié)果分別是什么吧
使用class類實(shí)現(xiàn)promise對(duì)象
class myPromise { constructor(executor) { this.status = 'pending'; // 變更promise的狀態(tài) this.value = null; executor(this.resolve, this.reject); // new 一個(gè)myPromise 得到的實(shí)例對(duì)象里面有兩個(gè)函數(shù) } resolve(value) { if (this.status !== 'pending') return; this.status = 'fulfilled'; // 變更promise的狀態(tài) this.value = value; } reject(reason) { if (this.status !== 'pending') return this.status = 'rejected'; this.value = reason; } }
貌似這么寫代碼邏輯也說得通,那么讓我們看一下實(shí)現(xiàn)的效果:
看到這里,不難想到我們的resolve和reject的兩個(gè)方法的this
指向出現(xiàn)了問題,我們仔細(xì)看看不難發(fā)現(xiàn),這兩個(gè)方法被我們作為實(shí)參放到了構(gòu)造器函數(shù),此時(shí)this的指向是指向了構(gòu)造器函數(shù),而不是我們寫的myPromise這個(gè)構(gòu)造函數(shù)身上,那只需要bind
顯示綁定一下this 的指向就可以解決了。
executor(this.resolve.bind(this), this.reject.bind(this))
并且myPromise的狀態(tài)一經(jīng)變更也不再改變,是不是有一點(diǎn)原裝Promise的味道了。但是在Promise里面還有一個(gè)錯(cuò)誤捕捉機(jī)制,只要promise里面執(zhí)行的邏輯報(bào)錯(cuò)了,就需要走reject邏輯,將錯(cuò)誤拋出來,那我們只需要使用try catch來實(shí)現(xiàn)就可以。
try { executor(this.resolve.bind(this), this.reject.bind(this)); } catch (error) { this.reject(error) }
這樣我們就實(shí)現(xiàn)了極簡(jiǎn)版的Promise對(duì)象,但是通常情況下我們都是使用Promise對(duì)象來處理異步的問題,說到異步,那不得不提起Promise.prototype.then()
這個(gè)方法了,then
方法返回的是一個(gè)新的Promise實(shí)例
(注意,不是原來那個(gè)Promise實(shí)例)。因此可以采用鏈?zhǔn)綄懛?,即then方法后面再調(diào)用另一個(gè)then方法。
then方法的第一個(gè)參數(shù)是`resolved狀態(tài)的回調(diào)函數(shù)`,第二個(gè)參數(shù)是`rejected狀態(tài)的回調(diào)函數(shù)`,它們都是可選的。
當(dāng)promise的狀態(tài)為'fulfilled'會(huì)執(zhí)行第一個(gè)回調(diào)函數(shù),當(dāng)狀態(tài)為'rejected'時(shí)執(zhí)行第二個(gè)回調(diào)函數(shù)。
必須等到Promise的狀態(tài)變更過一次之后,狀態(tài)為'fulfilled'或者'rejected',才去執(zhí)行then里面的邏輯。
.then支持鏈?zhǔn)秸{(diào)用,下一次.then受上一次.then執(zhí)行結(jié)果的影響。
知道以上這幾點(diǎn),我們就可以嘗試如何實(shí)現(xiàn).then方法了
class myPromise { constructor(executor) { this.status = 'pending'; this.value = null; this.onFulfilledCallbacks = []; // 用來保存成功的回調(diào)(處理異步) this.onRejectedCallbacks = []; // 用來保存失敗的回調(diào)(處理異步) try { executor(this.resolve.bind(this), this.reject.bind(this)); } catch (error) { this.reject(error) } } resolve(value) { if (this.status !== 'pending') return; this.status = 'fulfilled'; this.value = value; // 調(diào)用then里面的回調(diào) while (this.onFulfilledCallbacks.length) { // 當(dāng)異步成功回調(diào)數(shù)組中存在回調(diào)函數(shù),那就執(zhí)行 this.onFulfilledCallbacks.shift()(this.value) } } reject(reason) { if (this.status !== 'pending') return this.status = 'rejected'; this.value = reason; while (this.onRejectedCallbacks.length) { // 當(dāng)異步失敗回調(diào)數(shù)組中存在回調(diào)函數(shù),那就執(zhí)行 this.onRejectedCallbacks.shift()(this.value) } } then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val // 判斷.then的第一個(gè)參數(shù)是不是一個(gè)函數(shù),如果不是就直接作為結(jié)果返回 onRejected = typeof onRejected === 'function' ? onRejected : val => { throw val } // 判斷.then的第二個(gè)參數(shù)是不是一個(gè)函數(shù),如果不是就直接作為錯(cuò)誤返回 var thenPromise = new myPromise((resolve, reject) => { // 因?yàn)?then返回的是一個(gè)心的Promise對(duì)象 const resolvePromise = callback => { // 用于判斷回調(diào)函數(shù)的類型 setTimeout(() => { // 讓整個(gè)回調(diào)函數(shù)比同步代碼晚一點(diǎn)執(zhí)行,官方不是使用setTimeout實(shí)現(xiàn) try { const x = callback(this.value); if (x === thenPromise) { // 你正在返回自身 throw new Error('不允許返回自身!'); } if (x instanceof myPromise) { // 返回的是一個(gè)Promise對(duì)象 x.then(resolve, reject); } else { // 直接返回一個(gè)值,作為resolve的值,傳遞給下一個(gè).then resolve(x); } } catch (error) { reject(error); throw new Error(error) } }) } if (this.status === 'fulfilled') { resolvePromise(onFulfilled) } else if (this.status === 'rejected') { resolvePromise(onRejected) } else if (this.status === 'pending') { this.onFulfilledCallbacks.push(resolvePromise.bind(this, onFulfilled)); this.onRejectedCallbacks.push(resolvePromise.bind(this, onRejected)); } }) return thenPromise } }
寫在最后
最后和大家分享一下,我是如何一步一步實(shí)現(xiàn)簡(jiǎn)易版的Promise
首先從Promise構(gòu)造函數(shù)的特點(diǎn),三種狀態(tài),狀態(tài)一經(jīng)改變就不再變化,所以在resolve
和reject
的方法里面加上判斷,如果不是'pending'
狀態(tài),則直接return,這樣就實(shí)現(xiàn)了狀態(tài)一經(jīng)發(fā)生改變則不再變化,因?yàn)?then里面回調(diào)的執(zhí)行,是根據(jù)Promise的狀態(tài)來執(zhí)行,當(dāng)狀態(tài)為'fulfilled'
時(shí)才執(zhí)行.then第一個(gè)回調(diào)函數(shù),裝狀態(tài)為'rejected'
執(zhí)行.then第二個(gè)回調(diào)函數(shù),但是如果在Promise里面,在resolve
或者reject
的外面套上setTimeout,那么狀態(tài)變更會(huì)加入到下一次宏任務(wù)隊(duì)列里,那我們九就維護(hù)出兩個(gè)數(shù)組,用來存放未執(zhí)行的回調(diào),當(dāng)狀態(tài)改變之后,在對(duì)應(yīng)的resolve
和reject
方法里去判斷我們維護(hù)的未執(zhí)行的回調(diào)函數(shù)的數(shù)組里是否有未執(zhí)行的回調(diào),如果有直接調(diào)用掉,并且因?yàn)?then返回的是一個(gè)Promise對(duì)象,所以我們不能直接把'onFulfilled'
,或者'onRejected'
其中一個(gè)回調(diào)給返回出去,否則.then后面就不能再接.then,所以在then方法里面我們定義了一個(gè)resolvePromise函數(shù)
,其目的就是在返回的'onFulfilled'
,或者'onRejected'
外面套一層Promise對(duì)象,使得他后面能繼續(xù)接.then的回調(diào),在這個(gè)resolvePromise函數(shù)
內(nèi)部我們還添加了判斷回調(diào)的類型,在官方的定義的Promise對(duì)象中,規(guī)定了回調(diào)不能是原Promise對(duì)象,另外兩個(gè)判斷是回調(diào)是一個(gè)Promise對(duì)象,以及如果不是Promise對(duì)象,那就直接resolve()
出去
最后同步代碼會(huì)優(yōu)先于.then的執(zhí)行,因?yàn)?code>.then是異步代碼中的微任務(wù),只有宏任務(wù)執(zhí)行完之后,微任務(wù)才會(huì)執(zhí)行,所以在resolvePromise
的回調(diào)外面套一層setTimeout
,這樣返回出去的.then
的邏輯,會(huì)去到下一次的宏任務(wù)隊(duì)列,這樣就實(shí)現(xiàn)了.then的執(zhí)行會(huì)比同步代碼稍晚一些,但是官方并不是使用setTimeout
實(shí)現(xiàn)的。
到此這篇關(guān)于詳解JavaScript如何實(shí)現(xiàn)一個(gè)簡(jiǎn)易的Promise對(duì)象的文章就介紹到這了,更多相關(guān)JavaScript實(shí)現(xiàn)Promise對(duì)象內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
微信小程序使用map組件實(shí)現(xiàn)獲取定位城市天氣或者指定城市天氣數(shù)據(jù)功能
這篇文章主要介紹了微信小程序使用map組件實(shí)現(xiàn)獲取定位城市天氣或者指定城市天氣數(shù)據(jù)功能,涉及微信小程序map組件結(jié)合微信API獲取天氣信息相關(guān)操作技巧,需要的朋友可以參考下2019-01-01利用Bootstrap實(shí)現(xiàn)表格復(fù)選框checkbox全選
Bootstrap相信應(yīng)該不用多介紹,來自 Twitter,是目前最受歡迎的前端框架。這篇文章主要給大家介紹了如何利用Bootstrap實(shí)現(xiàn)表格中的checkbox復(fù)選框全選效果,文中給出詳細(xì)的介紹及完整的實(shí)例代碼,相信對(duì)大家的理解和學(xué)習(xí)具有一定的參考借鑒價(jià)值,下面來一起看看吧。2016-12-12js 為label標(biāo)簽和div標(biāo)簽賦值的方法
這篇文章介紹了js 為label標(biāo)簽和div標(biāo)簽賦值的方法,有需要的朋友可以參考一下2013-08-08three.js設(shè)置物體的縮放和旋轉(zhuǎn)代碼示例
最近在用three.js做三維模型的時(shí)候,需要通過鼠標(biāo)滑輪向前來控制視角朝鼠標(biāo)的位置放大,然后通過鼠標(biāo)滑輪向后將視角復(fù)原,這篇文章主要給大家介紹了關(guān)于three.js如何設(shè)置物體的縮放和旋轉(zhuǎn)的相關(guān)資料,需要的朋友可以參考下2023-11-11JavaScript中valueOf函數(shù)與toString方法深入理解
基本上,所有JS數(shù)據(jù)類型都擁有valueOf和toString這兩個(gè)方法,null除外。它們倆解決javascript值運(yùn)算與顯示的問題,本文將詳細(xì)介紹,有需要的朋友可以參考下2012-12-12基于javascript的無(wú)縫滾動(dòng)動(dòng)畫實(shí)現(xiàn)2
這篇文章主要介紹了基于javascript的無(wú)縫滾動(dòng)動(dòng)畫實(shí)現(xiàn)2,文章通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08