<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
實現一個簡易的Promise物件,我們首先要了解幾個相關的知識點:
Promise物件的狀態: pending(進行中)、fulfilled(已成功)和rejected(已失敗)。只有非同步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。這也是Promise這個名字的由來,它的英語意思就是“承諾”,表示其他手段無法改變。
Promise的引數: Promise建構函式接收一個函數作為引數,函數內部有兩個引數,分別是resolve和reject,這兩個引數是兩個函數,由JS引擎提供,不需要我們部署。reslove函數
的作用是將Promise物件的狀態由 'pending' 狀態變為 'resolved'狀態即('fulfilled'狀態),方便與引數名對應,reject函數
的作用是將Promise物件的狀態由 'pending' 狀態變為 'rejected'狀態。
但是我們應該注意的是,Promise物件的狀態一經改變,就不再發生改變(即pending
--> resolved
|| pending
--> rejected
其中任意一種發生改變之後,Promise物件的狀態將不再發生改變)
let p1 = new Promise((resolve, reject) => { resolve('成功'); reject('失敗'); throw('報錯'); //相當於reject() }) console.log(p1);
讓我們看看三種狀態的列印的結果分別是什麼吧
class myPromise { constructor(executor) { this.status = 'pending'; // 變更promise的狀態 this.value = null; executor(this.resolve, this.reject); // new 一個myPromise 得到的範例物件裡面有兩個函數 } resolve(value) { if (this.status !== 'pending') return; this.status = 'fulfilled'; // 變更promise的狀態 this.value = value; } reject(reason) { if (this.status !== 'pending') return this.status = 'rejected'; this.value = reason; } }
貌似這麼寫程式碼邏輯也說得通,那麼讓我們看一下實現的效果:
看到這裡,不難想到我們的resolve和reject的兩個方法的this
指向出現了問題,我們仔細看看不難發現,這兩個方法被我們作為實參放到了構造器函數,此時this的指向是指向了構造器函數,而不是我們寫的myPromise這個建構函式身上,那隻需要bind
顯示繫結一下this 的指向就可以解決了。
executor(this.resolve.bind(this), this.reject.bind(this))
並且myPromise的狀態一經變更也不再改變,是不是有一點原裝Promise的味道了。但是在Promise裡面還有一個錯誤捕捉機制,只要promise裡面執行的邏輯報錯了,就需要走reject邏輯,將錯誤丟擲來,那我們只需要使用try catch來實現就可以。
try { executor(this.resolve.bind(this), this.reject.bind(this)); } catch (error) { this.reject(error) }
這樣我們就實現了極簡版的Promise物件,但是通常情況下我們都是使用Promise物件來處理非同步的問題,說到非同步,那不得不提起Promise.prototype.then()
這個方法了,then
方法返回的是一個新的Promise範例
(注意,不是原來那個Promise範例)。因此可以採用鏈式寫法,即then方法後面再呼叫另一個then方法。
then方法的第一個引數是`resolved狀態的回撥函數`,第二個引數是`rejected狀態的回撥函數`,它們都是可選的。
當promise的狀態為'fulfilled'會執行第一個回撥函數,當狀態為'rejected'時執行第二個回撥函數。
必須等到Promise的狀態變更過一次之後,狀態為'fulfilled'或者'rejected',才去執行then裡面的邏輯。
.then支援鏈式呼叫,下一次.then受上一次.then執行結果的影響。
知道以上這幾點,我們就可以嘗試如何實現.then方法了
class myPromise { constructor(executor) { this.status = 'pending'; this.value = null; this.onFulfilledCallbacks = []; // 用來儲存成功的回撥(處理非同步) this.onRejectedCallbacks = []; // 用來儲存失敗的回撥(處理非同步) 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; // 呼叫then裡面的回撥 while (this.onFulfilledCallbacks.length) { // 當非同步成功回撥陣列中存在回撥函數,那就執行 this.onFulfilledCallbacks.shift()(this.value) } } reject(reason) { if (this.status !== 'pending') return this.status = 'rejected'; this.value = reason; while (this.onRejectedCallbacks.length) { // 當非同步失敗回撥陣列中存在回撥函數,那就執行 this.onRejectedCallbacks.shift()(this.value) } } then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val // 判斷.then的第一個引數是不是一個函數,如果不是就直接作為結果返回 onRejected = typeof onRejected === 'function' ? onRejected : val => { throw val } // 判斷.then的第二個引數是不是一個函數,如果不是就直接作為錯誤返回 var thenPromise = new myPromise((resolve, reject) => { // 因為.then返回的是一個心的Promise物件 const resolvePromise = callback => { // 用於判斷回撥函數的型別 setTimeout(() => { // 讓整個回撥函數比同步程式碼晚一點執行,官方不是使用setTimeout實現 try { const x = callback(this.value); if (x === thenPromise) { // 你正在返回自身 throw new Error('不允許返回自身!'); } if (x instanceof myPromise) { // 返回的是一個Promise物件 x.then(resolve, reject); } else { // 直接返回一個值,作為resolve的值,傳遞給下一個.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 } }
最後和大家分享一下,我是如何一步一步實現簡易版的Promise
首先從Promise建構函式的特點,三種狀態,狀態一經改變就不再變化,所以在resolve
和reject
的方法裡面加上判斷,如果不是'pending'
狀態,則直接return,這樣就實現了狀態一經發生改變則不再變化,因為.then裡面回撥的執行,是根據Promise的狀態來執行,當狀態為'fulfilled'
時才執行.then第一個回撥函數,裝狀態為'rejected'
執行.then第二個回撥函數,但是如果在Promise裡面,在resolve
或者reject
的外面套上setTimeout,那麼狀態變更會加入到下一次宏任務佇列裡,那我們九就維護出兩個陣列,用來存放未執行的回撥,當狀態改變之後,在對應的resolve
和reject
方法裡去判斷我們維護的未執行的回撥函數的陣列裡是否有未執行的回撥,如果有直接呼叫掉,並且因為.then返回的是一個Promise物件,所以我們不能直接把'onFulfilled'
,或者'onRejected'
其中一個回撥給返回出去,否則.then後面就不能再接.then,所以在then方法裡面我們定義了一個resolvePromise函數
,其目的就是在返回的'onFulfilled'
,或者'onRejected'
外面套一層Promise物件,使得他後面能繼續接.then的回撥,在這個resolvePromise函數
內部我們還新增了判斷回撥的型別,在官方的定義的Promise物件中,規定了回撥不能是原Promise物件,另外兩個判斷是回撥是一個Promise物件,以及如果不是Promise物件,那就直接resolve()
出去
最後同步程式碼會優先於.then的執行,因為.then
是非同步程式碼中的微任務,只有宏任務執行完之後,微任務才會執行,所以在resolvePromise
的回撥外面套一層setTimeout
,這樣返回出去的.then
的邏輯,會去到下一次的宏任務佇列,這樣就實現了.then的執行會比同步程式碼稍晚一些,但是官方並不是使用setTimeout
實現的。
到此這篇關於詳解JavaScript如何實現一個簡易的Promise物件的文章就介紹到這了,更多相關JavaScript實現Promise物件內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45