摘要:所以,這篇文章我會帶大家從零開始,手寫一個基本能用的。首先,規定對象是一個構造函數,用來生成實例。然后,這個構造函數接受一個函數作為參數,該函數的兩個參數分別是和。對象通過自身的狀態,來控制異步操作。
剛開始寫前端的時候,處理異步請求經常用callback,簡單又順手。后來寫著寫著就拋棄了callback,開始用promise來處理異步問題。promise寫起來確實更加優美,但由于缺乏對它內部結構的深刻認識,每次在遇到一些復雜的情況時,promise用起來總是不那么得心應手,debug也得搞半天。
所以,這篇文章我會帶大家從零開始,手寫一個基本能用的promise。跟著我寫下來以后,你會對promise是什么以及它的內部結構有一個清楚的認知,未來在復雜場景下使用promise也能如魚得水。
什么是Promise回到正文,什么是Promise?說白了,promise就是一個容器,里面保存著某個未來才會結束的事件(通常是一個異步操作)的結果。
首先,ES6規定Promise對象是一個構造函數,用來生成Promise實例。然后,這個構造函數接受一個函數(executor)作為參數,該函數的兩個參數分別是resolve和reject。最后,Promise實例生成以后,可以用then方法分別指定resolved狀態和rejected狀態的回調函數(onFulfilled和onRejected)。
具體的使用方法,用代碼表現是這樣:
const promise = new Promise(function(resolve, reject) { // ... some code if (/* 異步操作成功 */){ resolve(value); } else { reject(error); } }); promise.then(function(value) { // success }, function(error) { // failure });
理解了這個后,我們就可以大膽的開始構造我們自己的promise了,我們給它取個名字:CutePromise
實現一個Promise:CutePromise我們直接用ES6的class來創建我們的CutePromise,對ES6語法還不熟悉的,可以先讀一下我的另外兩篇介紹ES6核心語法的文章后再回來。30分鐘掌握ES6/ES2015核心內容(上)、30分鐘掌握ES6/ES2015核心內容(下)
class CutePromise { // executor是我們實例化CutePromise時傳入的參數函數,它接受兩個參數,分別是resolve和reject。 // resolve和reject我們將會定義在constructor當中,供executor在執行的時候調用 constructor(executor) { const resolve = () => {} const reject = () => {} executor(resolve, reject) } // 為實例提供一個then的方法,接收兩個參數函數, // 第一個參數函數必傳,它會在promise已成功(fulfilled)以后被調用 // 第二個參數非必傳,它會在promise已失敗(rejected)以后被調用 then(onFulfilled, onRejected) {} }
創建了我們的CutePromise后,我們再來搞清楚一個關鍵點:Promise 對象的狀態。
Promise 對象通過自身的狀態,來控制異步操作。一個Promise 實例具有三種狀態:
異步操作未完成(pending)
異步操作成功(fulfilled)
異步操作失敗(rejected)
上面三種狀態里面,fulfilled和rejected合在一起稱為resolved(已定型)。狀態的切換只有兩條路徑:第一種是從pending=>fulfilled,另一種是從pending=>rejected,狀態一旦切換就不能再改變。
現在我們來為CutePromise添加狀態,大概流程就是:
首先,實例化初始過程中,我們先將狀態設為PENDING,然后當executor執行resolve的時候,將狀態更改為FULFILLED,當executor執行reject的時候將狀態更改為REJECTED。同時更新實例的value。
constructor(executor) { ... this.state = "PENDING"; ... const resolve = (result) => { this.state = "FULFILLED"; this.value = result; } const reject = (error) => { this.state = "REJECTED"; this.value = error; } ... }
再來看下我們的then函數。then函數的兩個參數,onFulfilled表示當promise異步操作成功時調用的函數,onRejected表示當promise異步操作失敗時調用的函數。假如我們調用then的時候,promise已經執行完成了(當任務是個同步任務時),我們可以直接根據實例的狀態來執行相應的函數。假如promise的狀態還是PENDING, 那我們就將onFulfilled和onRejected直接存儲到chained這個變量當中,等promise執行完再調用。
constructor(executor) { ... this.state = "PENDING"; // chained用來儲存promise執行完成以后,需要被依次調用的一系列函數 this.chained = []; const resolve = (result) => { this.state = "FULFILLED"; this.value = result; // promise已經執行成功了,可以依次調用.then()函數里的onFulfilled函數了 for (const { onFulfilled } of this.chained) { onFulfilled(res); } } ... } then(onFulfilled, onRejected) { if (this.state === "FULFILLED") { onFulfilled(this.value); } else if (this.state === "REJECTED") { onRejected(this.value); } else { this.$chained.push({ onFulfilled, onRejected }); } }
這樣我們就完成了一個CutePromise的創建,下面是完整代碼,大家可以復制代碼到控制臺測試一下:
class CutePromise { constructor(executor) { if (typeof executor !== "function") { throw new Error("Executor must be a function"); } this.state = "PENDING"; this.chained = []; const resolve = res => { if (this.state !== "PENDING") { return; } this.state = "FULFILLED"; this.internalValue = res; for (const { onFulfilled } of this.chained) { onFulfilled(res); } }; const reject = err => { if (this.state !== "PENDING") { return; } this.state = "REJECTED"; this.internalValue = err; for (const { onRejected } of this.chained) { onRejected(err); } }; try { executor(resolve, reject); } catch (err) { reject(err); } } then(onFulfilled, onRejected) { if (this.state === "FULFILLED") { onFulfilled(this.internalValue); } else if (this.$state === "REJECTED") { onRejected(this.internalValue); } else { this.chained.push({ onFulfilled, onRejected }); } } }
提供一下測試代碼:
let p = new CutePromise(resolve => { setTimeout(() => resolve("Hello"), 100); }); p.then(res => console.log(res)); p = new CutePromise((resolve, reject) => { setTimeout(() => reject(new Error("woops")), 100); }); p.then(() => {}, err => console.log("Async error:", err.stack)); p = new CutePromise(() => { throw new Error("woops"); }); p.then(() => {}, err => console.log("Sync error:", err.stack));實現鏈式調用
實現鏈式調用其實很簡單,只需要在我們定義的then()方法里返回一個新的CutePromise即可。
then(onFulfilled, onRejected) { return new CutePromise((resolve, reject) => { const _onFulfilled = res => { try { //注意這里resolve有可能要處理的是一個promise resolve(onFulfilled(res)); } catch (err) { reject(err); } }; const _onRejected = err => { try { reject(onRejected(err)); } catch (_err) { reject(_err); } }; if (this.state === "FULFILLED") { _onFulfilled(this.internalValue); } else if (this.state === "REJECTED") { _onRejected(this.internalValue); } else { this.chained.push({ onFulfilled: _onFulfilled, onRejected: _onRejected }); } }); }
不過,我們還需要解決一個問題:假如then函數的第一個參數onfulfilled()本身返回的也是一個promise怎么辦?比如下面這種使用方式,其實是最真實項目場景中最常見:
p = new CutePromise(resolve => { setTimeout(() => resolve("World"), 100); }); p. then(res => new CutePromise(resolve => resolve(`Hello, ${res}`))). then(res => console.log(res));
所以我們需要讓我們的resolve方法能夠處理promise:
const resolve = res => { if (this.state !== "PENDING") { return; } // 假如說res這個對象有then的方法,我們就認為res是一個promise if (res != null && typeof res.then === "function") { return res.then(resolve, reject); } ... }三道思考題
promise array的鏈式調用?
promise怎么做并發控制?
promise怎么做異步緩存?
以上三道思考題其實跟你用不用promise并沒有多大關系,但是如果你不深刻理解promise想要解決這三個問題還真不是那么輕松的。
參考:https://brunoscopelliti.com/l...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/94342.html
摘要:是什么在規范中,是一個類,它的構造函數接受一個函數。在這種情況下,是但處于狀態。與一起使用關鍵字會暫停執行一個函數,直到等待的變成狀態。此外,會一直等待調用直到下一個時序。 原文:Write Your Own Node.js Promise Library from Scratch作者:code_barbarian Promise 已經是 JavaScript 中異步處理的基石,回調...
摘要:從零開始搭建同構應用二項目工程化瀏覽器端在從零開始同構開發一中我們已經能實現基本的配置和編譯了。接下來我們需要將編譯工作工程化。配置作用自動生成自動在引入,。文件內容如下同構開發配置自動刷新這里我們用到的修飾器特性。 從零開始搭建React同構應用(二) 項目工程化(瀏覽器端) 在從零開始React同構開發(一)中我們已經能實現基本的React配置和編譯了。接下來我們需要將編譯工作工程...
摘要:從最開始的到封裝后的都在試圖解決異步編程過程中的問題。為了讓編程更美好,我們就需要引入來降低異步編程的復雜性。寫一個符合規范并可配合使用的寫一個符合規范并可配合使用的理解的工作原理采用回調函數來處理異步編程。 JavaScript怎么使用循環代替(異步)遞歸 問題描述 在開發過程中,遇到一個需求:在系統初始化時通過http獲取一個第三方服務器端的列表,第三方服務器提供了一個接口,可通過...
摘要:模塊化是隨著前端技術的發展,前端代碼爆炸式增長后,工程化所采取的必然措施。目前模塊化的思想分為和。特別指出,事件不等同于異步,回調也不等同于異步。將會討論安全的類型檢測惰性載入函數凍結對象定時器等話題。 Vue.js 前后端同構方案之準備篇——代碼優化 目前 Vue.js 的火爆不亞于當初的 React,本人對寫代碼有潔癖,代碼也是藝術。此篇是準備篇,工欲善其事,必先利其器。我們先在代...
閱讀 2330·2021-09-30 09:47
閱讀 2949·2019-08-30 11:05
閱讀 2526·2019-08-29 17:20
閱讀 1912·2019-08-29 13:01
閱讀 1721·2019-08-26 13:39
閱讀 1221·2019-08-26 13:26
閱讀 3205·2019-08-23 18:40
閱讀 1810·2019-08-23 17:09