做者: JowayYoung
倉庫: Github、 CodePen
博客: 掘金、 思否、 知乎、 簡書、 頭條、 CSDN
公衆號: IQ前端
聯繫我:關注公衆號後有個人 微信喲
特別聲明:原創不易,未經受權不得對此文章進行轉載或抄襲,不然按侵權處理,如需轉載或開通公衆號白名單可聯繫我,但願各位尊重原創的知識產權
本文由筆者師妹LazyCurry創做,收錄於筆者技術文章專欄下前端
咱們都知道,JS是單線程的,只有前一個任務結束,才能執行下一個任務。顯然在瀏覽器上,這樣執行會堵塞瀏覽器對DOM的渲染。因此,JS中會有不少異步操做,那JS是如何實現異步操做呢?這就要想到Promise對象了,文本先來認識Promise,再手寫代碼實現Promise。git
Promise是JS解決異步編程的方法之一,其英文意思是承諾。在程序中可理解爲等一段時間就會執行,等一段時間就是JS中的異步。異步是指須要比較長的時間才能執行完成的任務,例如網絡請求,讀取文件等。Promise是一個實例對象,可從中獲取異步處理的結果。github
Promise有3種狀態,分別是pending
(進行中)、fulfilled
(已成功)、rejected
(已失敗)。只有異步操做可改變Promise的狀態,其餘操做都沒法改變。而且狀態改變後就不會再變,只能是從pending
到fulfiled
或pending
到rejected
,這也是Promise一個比較鮮明的特色。編程
上述已說到,Promise是一個對象,那麼它確定是由其構造函數來建立。其構造函數接受一個函數做爲參數,其函數的參數有2個,分別是resolve
和reject
。resolve將狀態從pending變爲fulfiled
,成功時調用。reject將狀態從pending變爲rejected
,失敗時調用。segmentfault
function RunPromise(num, time) { return new Promise((resolve, reject) => { console.log("開始執行"); if (num % 2 === 0) { setTimeout(() => { resolve(`偶數時調用resolve,此時num爲${num}`); }, time); } else { setTimeout(() => { reject(new Error(`奇數時調用rejected,此時num爲${num}`)); }, time); } }); }
Promise對象上有then()
和catch()
方法。then()
接收2個參數,第一個對應resolve的回調,第二個對應reject的回調。catch()
跟then()
的第二個參數同樣,用來接受reject的回調,可是還有一個做用,若是在then()
中執行resolve回調時拋出異常,這個異常多是代碼定義拋出,也多是代碼錯誤,而這個異常會在catch()
被捕獲到。數組
RunPromise(22, 2000) .then(res => { console.log("then的第一個參數執行"); console.log(res); console.log(newres); }, error => { console.log("then的第二個參數執行"); console.log(error); }) .catch(error => { console.log("error"); console.log(error); }); // 輸出結果以下: // 開始執行 // then的第一個參數執行 // 偶數時調用resolve,此時num爲22 // error // ReferenceError: newres is not defined
上面例子中,RunPromise()
調用resolve
,then()
的第一個參數對應回調,狀態從pending
改爲fulfilled
,且狀態不會再改變。在then()
中,newres這個變量還沒有定義,所以程序出錯,其異常在catch()
被捕獲。通常來講,then()
使用第一個參數便可,由於catch()
跟then()
的第二個參數同樣,還能捕獲到異常。promise
Promise大體已瞭解清楚,也知道如何使用。爲了瞭解Promise是如何實現的,咱們手寫實現一個簡單的Promise方法,簡單地實現then()、異步處理、鏈式調用。用最簡單的思考方法,函數是爲了實現什麼功能,給對應函數賦予相應的實現代碼便可。如下代碼均使用ES6
進行書寫。瀏覽器
定義Promise構造函數
建立Promise對象使用new Promise((resolve, reject) => {})
,可知道Promise構造函數的參數是一個函數,咱們將其定義爲implement
,函數帶有2個參數:resolve
,reject
,而這2個參數又可執行,因此也是一個函數。微信
聲明完成後,須要解決狀態。上述已說過,Promise有3種狀態,這裏再也不細說,直接上代碼。網絡
// ES6聲明構造函數 class MyPromise { constructor(implement) { this.status = "pending"; // 初始化狀態爲pending this.res = null; // 成功時的值 this.error = null; // 失敗時的值 const resolve = res => { // resolve的做用只是將狀態從pending轉爲fulfilled,並將成功時的值存在this.res if (this.status === "pending") { this.status = "fulfilled"; this.res = res; } }; const reject = error => { // reject的做用只是將狀態從pending轉爲rejected,並將失敗時的值存在this.error if (this.status === "pending") { this.status = "rejected"; this.error = error; } }; // 程序報錯時會執行reject,因此在這裏加上錯誤捕獲,直接執行reject try { implement(resolve, reject); } catch (err) { reject(err); } } }
then函數
咱們在使用Promise時,都知道then()
有2個參數,分別是狀態爲fulfilled
和rejected
時的回調函數,咱們在這裏將2個函數定義爲onFulfilled
和onRejected
。
class MyPromise { constructor(implement) { ... } then(onFulfilled, onRejected) { // 當狀態爲fulfilled時,調用onFulfilled並傳入成功時的值 if (this.status === "fulfilled") { onFulfilled(this.res); } // 當狀態爲rejected時,調用onRejected並傳入失敗時的值 if (this.status === "rejected") { onRejected(this.error); } } }
異步處理
到這裏已實現了基本的代碼,可是異步時會出現問題。例如,本文一開始舉例使用Promise時,resolve
在setTimeout()
中使用,這時候在then()
裏,狀態仍是pending
,那就沒辦法調用到onFulfilled
。因此咱們先將處理函數(onFulfilled
或onRejected
)保存起來,等到then()
被調用時再使用這些處理函數。
由於Promise可定義多個then()
,因此這些處理函數用數組進行存儲。實現思路:
then()
增長狀態爲pending的判斷,在此時存儲處理函數resolve
或reject
時循環調用處理函數class MyPromise { constructor(implement) { this.status = "pending"; this.res = null; this.error = null; this.resolveCallbacks = []; // 成功時回調的處理函數 this.rejectCallbacks = []; // 失敗時回調的處理函數 const resolve = res => { if (this.status === "pending") { this.status = "fulfilled"; this.res = res; this.resolveCallbacks.forEach(fn => fn()); // 循環執行成功處理函數 } }; const reject = error => { if (this.status === "pending") { this.status = "rejected"; this.error = error; this.rejectCallbacks.forEach(fn => fn()); // 循環執行失敗處理函數 } }; try { implement(resolve, reject); } catch (err) { reject(err); } } then(onFulfilled, onRejected) { if (this.status === "fulfilled") { onFulfilled(this.res); } if (this.status === "rejected") { onRejected(this.error); } // 當狀態爲pending時,說明這時尚未調用到resolve或reject // 在這裏把成功函數和失敗函數存至相應的數組中,不作執行操做只作存儲操做 if (this.status === "pending") { this.resolveCallbacks.push(() => onFulfilled(this.res)); this.rejectCallbacks.push(() => onRejected(this.error)); } } }
測試一下異步功能,打印結果中,'執行resolve'是等待了2秒後打印出來的
new MyPromise((resolve, reject) => { console.log("開始執行"); setTimeout(() => { resolve("執行resolve"); }, 2000); }).then(res => console.log(res)); // 輸出結果以下: // 開始執行 // 執行resolve
鏈式調用
到這裏就已實現異步操做啦!吼吼~可是,咱們都知道,Promise能定義多個then,就例如new Promise().then().then()
,這種就是鏈式調用。固然咱們也要實現這個功能。
鏈式調用是指Promise在狀態是fulfilled
後,又開始執行下一個Promise。要實現這個功能,咱們只須要在then()
裏返回Promise就行了,提及來好像是挺簡單的。
then()
的實現思路:
then()
中須要返回Promise對象,咱們將其命名爲nextPromise
onFulfilled
和onRejected
是異步調用,用setTimeout(0)
解決onFulfilled
和onRejected
類型作判斷,並作相應返回class MyPromise { constructor(implement) { ... } then(onFulfilled, onRejected) { // 若是onRejected不是函數,就直接拋出錯誤 onFulfilled = typeof onFulfilled === "function" ? onFulfilled : res => res; onRejected = typeof onRejected === "function" ? onRejected : err => { throw err; }; const nextPromise = new MyPromise((resolve, reject) => { if (this.status === "fulfilled") { // 解決異步問題 setTimeout(() => { const x = onFulfilled(this.res); RecursionPromise(nextPromise, x, resolve, reject); }, 0); } if (this.status === "rejected") { setTimeout(() => { const x = onRejected(this.error); RecursionPromise(nextPromise, x, resolve, reject); }, 0); } if (this.status === "pending") { this.resolveCallbacks.push(() => { setTimeout(() => { const x = onFulfilled(this.res); RecursionPromise(nextPromise, x, resolve, reject); }, 0); }); this.rejectCallbacks.push(() => { setTimeout(() => { const x = onRejected(this.error); RecursionPromise(nextPromise, x, resolve, reject); }, 0); }); } }); return nextPromise; } }
RecursionPromise()
用來判斷then()
的返回值,以決定then()
向下傳遞的狀態走resolve
仍是reject
,實現思路:
nextPromise
與x
不能相等,不然會一直調用本身x
的類型,若是不是函數或對象,直接resolve(x)
x
是否擁有then()
,而且若是then()
是一個函數,那麼就可執行x
的then()
,而且帶有成功與失敗的回調flag
的做用是執行x
的then()
時成功與失敗只能調用一次x
的then()
,成功時繼續遞歸解析then()
不是一個函數,直接resolve(x)
function RecursionPromise(nextPromise, x, resolve, reject) { if (nextPromise === x) return false; let flag; if (x !== null && (typeof x === "object" || typeof x === "function")) { try { let then = x.then; if (typeof then === "function") { then.call(x, y => { if (flag) return false; flag = true; // 這裏說明Promise對象resolve以後的結果仍然是Promise,那麼繼續遞歸解析 RecursionPromise(nextPromise, y, resolve, reject); }, error => { if (flag) return false; flag = true; reject(error); }); } else { resolve(x); } } catch (e) { if (flag) return false; flag = true; reject(e); } } else { resolve(x); } }
具備異步處理
和鏈式調用
的Promise已實現啦!還有一些方法在這裏就不一一實現了。畢竟實現一個完整的Promise不是一篇文章就能講完的,有興趣的同窗可自行參照Promise的功能進行解構重寫,如有寫得不正確的地方請各位大佬指出。公衆號後臺回覆promise可獲取本文的源碼,若是是轉載的文章,可關注IQ前端再回復promise便可。
寫這篇文章的目的是爲了給各位同窗提供一個函數解構的思路,學會去分析一個函數的功能,從而解構出每個步驟是如何執行和實現的,祝你們學習愉快,下次再見~
❤️關注+點贊+收藏+評論+轉發❤️,原創不易,鼓勵筆者創做更好的文章
關注公衆號IQ前端
,一個專一於CSS/JS開發技巧的前端公衆號,更多前端小乾貨等着你喔
關鍵詞
免費領取視頻教程我微信
拉你進技術交流羣IQ前端
,更多CSS/JS開發技巧只在公衆號推送