天天看點

js promise的用法_前端猛男帶你了解promise高階函數JS 異步程式設計手寫Promise總結

相信很多人都對 es6 文法中的 promise 比較熟悉,但是大部分人隻知道它的用法,卻沒有真正去了解它的内在實作。下面就讓筆者帶領大家去一探究竟,并且手寫一個類似 promise 的類實作。文章内容主要分為三個部分:1、高階函數;2、js 異步程式設計;3、手寫 promise 源碼。這時候就會有人疑惑,前面兩部分是幹嘛用的?在筆者看來,前面兩個章節可以說是對 promise 原理的鋪墊以及擴充。

高階函數

在 js 語言中,函數也可以像普通 object 一樣成為其他函數裡的參數或是傳回值。我們将參數或是傳回值為函數的函數稱為高階函數。下面來看一下它的基本用法: 用法看起來并不困難,那使用它對于我們日常的開發有什麼好處呢?下面介紹兩種比較常用的使用場景:

1、函數柯裡化

把接受多個參數的函數變換成接受部分參數的函數,并且傳回接受餘下的參數而且傳回結果的新函數的技術,就是函數柯裡化。聽起來可能有點拗口,那我們先來看看下面這個熟悉的面試題 其實這道題就是一個函數柯裡化的實作。當函數被柯裡化以後,一旦傳入的參數少于函數定義的參數數量,就會傳回一個接受目前傳入的部分參數的新函數,是以上面三行代碼的執行結果才能一緻。下面讓我們來看看這是如何實作的:先定義一個函數: 這裡使用了遞歸、閉包、es6 的展開運算符、高階函數相關知識,有興趣的小夥伴可以自行查閱資料,這裡筆者就不再一一細講了說完函數柯裡化的實作以後,也來說一說它能應用在哪些地方:

(1)參數複用

當一個函數接受多個參數時,如果其中一個參數基本保持不變且作為基準值供後續參數使用,函數柯裡化就會大派用場,例如一些固定正規表達式的校驗,以及一些固定資料源的處理

(2)延遲執行

顧名思義,就是等到所有的參數都傳入時才執行,我們在平常使用的 bind 函數實際就是柯裡化的一種實作

2、react 的高階元件

寫過 react 的小夥伴應該都使用過它的高階元件,它的目的也是為了盡量提取重複的代碼進行封裝,以達到代碼優化的效果,這裡就暫時不展開描述,有緣的話我們下次分解。

JS 異步程式設計

為什麼會說到這個子產品呢?因為 promise 的執行過程就是在微任務裡面執行的。下面讓我們循序漸進的進行介紹:

1、js 異步程式設計的基本了解

Javascript 語言的執行環境是"單線程",也就是指一次隻能完成一件任務。如果有多個任務,就必須排隊,前面一個任務完成,再執行後面一個任務。為了避免其中一個任務執行時間過長導緻其他任務無法執行的問題,js 将任務分為同步和異步任務。常見的異步任務有:回調函數(包含事件回調、釋出訂閱)、setTimeout 和 setInterval 定時器方法、promise

2、EventLoop和消息隊列

js promise的用法_前端猛男帶你了解promise高階函數JS 異步程式設計手寫Promise總結

上述圖表清晰的展示了代碼的執行過程,下面分别對其中的一些概念進行解釋:eventLoop指的是調用棧(call stack)、事件映射表(event table)以及消息隊列(event queue)這三者結合的環路。消息隊列(event queue),也叫回調函數隊列,當event table中的事件被觸發,對應的回調函數就是被推進消息隊列,等待被推進call stack執行。

3、宏任務和微任務

宏任務可以了解為每次調用棧從開始執行到結束執行(調用棧清空)的過程(注意,這裡指的并不是每行代碼的執行)微任務指的是目前宏任務執行完畢以後,下一個宏任務執行之前的過渡任務, 它會在目前宏任務執行完畢以後立即執行promise的執行就是在微任務裡面進行的,下面來看一下一個經典的面試題: 這裡的輸出結果先賣個關子,這裡的代碼執行涉及到宏任務、微任務以及async、await的原理剖析,筆者在最後會為大家再進行的執行順序的分析

手寫Promise

下面就讓我們來手寫一個類似的promise的東西。筆者會根據promise的特征一步步進行完善

1、promise基礎特征

promise是使用new來進行執行個體化,并且裡面傳入一個函數做為執行個體化的參數,該函數調用類内部的resolve和reject方法,那下面我們來建立一個基礎類 由于 executor為外部傳入函數,為了使用執行個體内部的函數,resolve和reject需要使用箭頭函數的形式(跟react裡面的函數用法相似) promise後面可接then進行鍊式調用,這跟函數柯裡化很相似,那大家就可以猜到傳回的其實是一個新的promise執行個體,then裡面接收兩個函數(成功回調函數和失敗回調函數) 這個時候promise的基礎結構已經完成,我們再來逐漸完善。

2、promise的狀态變化

在promise裡面resolve和reject分别代表成功和失敗的狀态,同時成功和失敗也有對應的值。promise主要根據這些狀态值進行下一步的處理 上述例子同時說明了一個promise的狀态變化邏輯,即隻能從pending變成resovled或者rejected,其它狀态值變化并不允許

3、promise的異步處理

非異步的情況我們已經實作了,下面來看看異步又有什麼辦法處理呢?首先then的異步情況分為兩種情況:1、同一個promise可以不斷調用then,這個時候then方法之間沒有任何聯系,對于同一個PromiseValue進行操作2、then的鍊式調用,由于每次then都會傳回一個promise,需要使用閉包的方式儲存每一個promise

(1)異步情況一

情景如下: 我們可以思考一下,異步最終都會走resolve和reject方法,是以我們隻要将執行的函數存儲起來,等到resolve或者reject方法執行就行,為此,我們需要成功回調函數集合以及失敗回調函數集合

(2)異步情況二

情景如下: 由于每次then都會傳回一個promise,需要使用閉包的方式儲存每一個promise,代碼改造如下: 上面的代碼還有一個需要注意的地方,為了能夠在promise執行個體内部拿到該執行個體自己,需要使用setTimeout函數進行處理,因為promise在微任務執行完畢以後,才會執行宏任務中的setTimeout,這也是我們在實踐中經常使用到的方法。

4、promise的錯誤處理

我們需要對promise内部所有執行的地方進行錯誤捕捉,由于這部分比較簡單,直接上代碼

5、promise的一些api實作

在使用promise的時候,我們有一些内部方法,例如new Promise().catch(),new Promise().finally();還有一些靜态方法Promise.all(),Promise.resolve(),下面讓我們來一一實作 至此,一個類似promise的實作已經基本完成

6、async和await的實作

promise在實際應用中經常搭載async和await使用,下面來介紹一下async和await的原理:實際上它是es6文法中generator的文法糖,那generator的特點如下: 輸出結果如下:

js promise的用法_前端猛男帶你了解promise高階函數JS 異步程式設計手寫Promise總結

隻有調用next方法,generator裡面相應的yield到下一個yield之間的代碼才會執行,next裡面還可以傳入值來給generator内部使用,上面改造一下: 這個時候的輸出為:

js promise的用法_前端猛男帶你了解promise高階函數JS 異步程式設計手寫Promise總結

緊接着讓我們根據下面的代碼來重寫async和await: generator的寫法: 可以看到這兩個輸出是完全一樣的!有了這個例子,相信大家對于宏任務和微任務裡面的那道面試題也是很清楚了!

總結

promise在實際應用中經常作為異步轉化為同步的解決方案,手寫一個promise類可以讓我們在實際應用中能夠更加得心應手。同時在前端面試中也經常會詢問到宏任務和微任務執行先後順序的相關問題,這篇文章也許可以讓你更加了解内在一些執行過程。最後,如果覺得文章寫得還可以,就給筆者來個大大的贊!如果發現寫得不好或者不對的地方,也歡迎給筆者留言進行更正。

繼續閱讀