摘要:并在其中判斷當前狀態。如果在異步調用完成之后才被調用方法,則我們無法把異步調用的結果傳遞給回調函數。我們對可能出現的異常進行捕獲,并將異常傳遞給。該實現的功能較為簡陋,僅實現了部分規范。
前端雜談: 如何實現一個 Promise? 首先, 什么是 Promise?
A promise is an object that may produce a single value some time in the future: either a resolved value, or a reason that it’s not resolved (e.g., a network error occurred). A promise may be in one of 3 possible states: fulfilled, rejected, or pending. Promise users can attach callbacks to handle the fulfilled value or the reason for rejection.
關鍵語句: Promise 是一個在將來某個時刻產生一個單一結果的對象.
通俗一點來說, Promise 代表了一個值, 但是這個值我們并不確定什么時候會被返回.
A promise is an object that may produce a single value some time in the future.簡單看看 Promise 的歷史
Promise 在 1980 年代被創建出來 =>
在 1988 年正式得名: Promise =>
已經有很多人了解到了 Promise, 但是人們還是堅持使用 node.js 中提倡的以回調函數首個參數傳 error 對象的方式處理異步代碼. =>
Dojo 首次大規模的使用了 Promise , 相應的 Promise/A 被提出用以規范 Promise 的實現 =>
JQuery 開始使用 Promise 并真正使 Promise 廣為人知 =>
JQuery 沒有實現部分 Promise 的功能, 這也導致了 Promie/A+ 標準的產生 =>
ES6 正式引入了 Promise,并且和已有的實現了 Promise/A 規范的 library 相兼容 =>
實現 Promise 之前, 讓我們看看 Promise 有哪些規范Promise 是一個 thenable 對象, 也就是說 Promise 有一個 .then() 方法
一個 pending 狀態的 Promise 可以進入 fulfilled 和 rejected 狀態
promise 一旦進入 fulfilled 或 rejected 狀態, 不可再改變其狀態
一旦 promise 改變了其狀態, 它筆芯有一個值(這個值也可能是 undefined)
開始實現一個 Promise首先, 讓我們看看一段最普通的異步代碼:
// 異步方法定義 var basicAsyncFunc = function(callback) { setTimeout(function() { var randomNumber = Math.random() if (randomNumber > 0.5) { callback(null, randomNumber) } else { callback(new Error("bad luck...")) } }, 1000) } // 異步方法調用 basicAsyncFunc((err, result) => { if (err) { console.log(`the reason fail is: ${err}`) return } console.log(`success get result: ${result}`) })
按照 Promise 的規范定義, 理想中 Promise 的調用方式為:
// Promise 形式的異步方法定義 var promiseAsyncFunc = function() {} // Promise 形式的異步方法調用 promiseAsyncFunc.then( data => { console.log(`success get result: ${data}`) }, err => { console.log(`the reason fail is: ${err}`) } )
按照這個理想當中的調用方式, 讓我們寫出第一版代碼.
第一版 Promise:能保存回調方法// Promise 形式的異步方法定義 var promiseAsyncFunc = function() { var fulfillCallback var rejectCallback setTimeout(() => { var randomNumber = Math.random() if (randomNumber > 0.5) fulfillCallback(randomNumber) else rejectCallback(randomNumber) }, 1000) return { then: function(_fulfillCallback, _rejectCallback) { fulfillCallback = _fulfillCallback rejectCallback = _rejectCallback } } } // Promise 形式的異步方法調用 promiseAsyncFunc().then(fulfillCallback, rejectCallback)
我們的思路是在 .then() 方法中, 將 fullfill 和 reject 結果的回調函數保存下來, 然后在異步方法中調用. 因為是異步調用, 根據 event-loop 的原理, promiseAsyncFunc().then(fulfillCallback, rejectCallback) 傳入的 callback 在異步調用結束時一定是已經賦值過了.
第二版 Promise:實構造函數當前我們的實現 Promise 中,異步邏輯代碼和 Promise 的代碼是雜糅在一起的,讓我們將其區分開:
var promiseAsyncFunc = function() { var fulfillCallback var rejectCallback return { fulfill: function(value) { if (fulfillCallback && typeof fulfillCallback === "function") { fulfillCallback(value) } }, reject: function(err) { if (rejectCallback && typeof rejectCallback === "function") { rejectCallback(err) } }, then: function(_fulfillCallback, _rejectCallback) { fulfillCallback = _fulfillCallback rejectCallback = _rejectCallback } } } let ownPromise = function(asyncCall) { let promise = promiseAsyncFunc() asyncCall(promise.fulfill, promise.reject) return promise } // Promise 形式的異步方法調用 ownPromise(function(fulfill, reject) { setTimeout(() => { var randomNumber = Math.random() if (randomNumber > 0.5) fulfill(randomNumber) else reject(randomNumber) }, 1000) })
我們新定義了一個方法 ownPromise() 用于創建 Promise,并在promiseAsyncFunc() 中暴露出 fulfill 和 reject 接口方便異步代碼去調用。
這里有一個問題,我們在調用 ownPromise()后得到了 promise 實例,此時我們可以直接調用 fulfill(),reject()這兩個方法,而理論上我們應該只應暴露 promise 的then()方法。所以我們利用閉包將這兩個方法隱藏:
var promiseAsyncFunc = function() { var fulfillCallback var rejectCallback return { fulfill: function(value) { if (fulfillCallback && typeof fulfillCallback === "function") { fulfillCallback(value) } }, reject: function(err) { if (rejectCallback && typeof rejectCallback === "function") { rejectCallback(err) } }, promise: { then: function(_fulfillCallback, _rejectCallback) { fulfillCallback = _fulfillCallback rejectCallback = _rejectCallback } } } } let ownPromise = function(asyncCall) { let defer = promiseAsyncFunc() asyncCall(defer.fulfill, defer.reject) return defer.promise } // Promise 形式的異步方法調用 ownPromise(function(fulfill, reject) { setTimeout(() => { var randomNumber = Math.random() if (randomNumber > 0.5) fulfill(randomNumber) else reject(randomNumber) }, 1000) })第三版 Promise: 支持狀態管理
為了實現規范中對于 Promise 狀態變化的要求, 我們需要為 Promise 加入狀態管理, 這一步較為簡單, 讓我們看代碼:
const PENDING = Symbol("pending") const FULFILLED = Symbol("fulfilled") const REJECTED = Symbol("rejected") // Promise 形式的異步方法定義 var promiseAsyncFunc = function() { var status = PENDING var fulfillCallback var rejectCallback return { fulfill: function(value) { if (status !== PENDING) return if (typeof fulfillCallback === "function") { fulfillCallback(value) status = FULFILLED } }, reject(error) { if (status !== PENDING) return if (typeof rejectCallback === "function") { rejectCallback(error) status = REJECTED } }, promise: { then: function(_fulfillCallback, _rejectCallback) { fulfillCallback = _fulfillCallback rejectCallback = _rejectCallback } } } } let ownPromise = function(asyncCall) { let defer = promiseAsyncFunc() asyncCall(defer.fulfill, defer.reject) return defer.promise } // Promise 形式的異步方法調用 ownPromise(function(fulfill, reject) { setTimeout(() => { var randomNumber = Math.random() if (randomNumber > 0.5) fulfill(randomNumber) else reject(randomNumber) }, 1000) }).then(data => console.log(data), err => console.log(err))
這段代碼中我們用到了 Symbol 來表示狀態常量, 對 Symbol 不了解的同學可以看這里
為了判斷 Promise 的狀態, 我們加入了 fulfill 和 reject 兩個方法。并在其中判斷 promise 當前狀態。如果不是 pending 狀態則直接 return(因為 Promise 狀態只可能改變一次)。
現在我們的 promise 實現了對狀態控制的規范:
只允許改變一次狀態
只能從 pending => fulfilled 或 pending => rejected
但是我們的 Promise 有一個問題: promise 的值沒有被保存下來。如果 promise 在異步調用完成之后才被調用 .then() 方法,則我們無法把異步調用的結果傳遞給回調函數。為此我們需要為 Promise 加一個 value 字段:
第四版 Promise: 保存異步調用的結果我們為 promise 加入 value 字段,用于保存 Promise 的執行結果。
// Promise 形式的異步方法定義 var promiseAsyncFunc = function() { var status = PENDING var fulfillCallback var rejectCallback var value return { fulfill: function(_value) { if (status !== PENDING) return value = _value status = FULFILLED if (typeof fulfillCallback === "function") { fulfillCallback(value) } }, reject(error) { if (status !== PENDING) return value = error status = REJECTED if (typeof rejectCallback === "function") { rejectCallback(error) } }, promise: { then: function(_fulfillCallback, _rejectCallback) { fulfillCallback = _fulfillCallback rejectCallback = _rejectCallback } } } }
這里我們又發現一個問題,如果一個 Promise 已經是fulfill或reject狀態。我們再調用 then() 方法時,傳入的回調方法永遠不會被調用(因為 status 已經不是 pending)。
所以我們需要在 then()方法中對其狀態進行判斷:
// Promise 形式的異步方法定義 var promiseAsyncFunc = function() { var status = PENDING var fulfillCallback var rejectCallback var value return { fulfill: function(_value) { if (status !== PENDING) return value = _value status = FULFILLED if (typeof fulfillCallback === "function") { fulfillCallback(value) } }, reject(error) { if (status !== PENDING) return value = error status = REJECTED if (typeof rejectCallback === "function") { rejectCallback(error) } }, promise: { then: function(_fulfillCallback, _rejectCallback) { if (status === REJECTED) { _rejectCallback(value) return } if (status === FULFILLED) { _fulfillCallback(value) return } fulfillCallback = _fulfillCallback rejectCallback = _rejectCallback } } } }第五版 Promise: 支持鏈式調用
為了支持鏈式調用,.then() 方法的返回值必須是用 thenable (根據 Promise/A+ 規范, .then() 方法的返回值需要是一個新的 Promise)
為此我們加入一個工具方法 makeThenable()。如果傳入的 value 本身就有 then()方法,則直接返回 value。否則返回一個有 then()方法的對象。
在該對象的 then()方法中,我們根據 promise 的狀態,調用不同的回調方法生成新的 value。
function makeThenable(value, status){ if(value && typeof value.then === "function"){ return value } if(status === FULFILLED){ return { then: function(fulfillCallback, rejectCallback){ return makeThenable(fulfillCallback(value), FULFILLED) } } } if(status === REJECTED) { return { then: function(fulfillCallback, rejectCallback){ return makeThenable(rejectCallback(value), FULFILLED) } } } }
有了以上的 makeThenable()方法,我們可以在 promise 的fulfill(),reject()回將 value 設置為 thenable:
var promiseAsyncFunc = function() { var status = PENDING var fulfillCallback var rejectCallback var value return { fulfill: function(_value) { if (status !== PENDING) return value = makeThenable(_value, FULFILLED) // 保證當前promise的value為 thenable status = FULFILLED if (typeof fulfillCallback === "function") { value.then(fulfillCallback) } }, reject(error) { if (status !== PENDING) return value = makeThenable(error, REJECTED) 、、 // 保證當前value為 thenable status = REJECTED if (typeof rejectCallback === "function") { value.then(null, rejectCallback) } }, promise: { then: function(){} } } }
接下來讓我們看 then()方法。為了返回一個新的 promise,我們首先得創建一個新的 promise。其次當前 promise 在fulfill() 或 reject()時,應該調用新的 promise 的fullfill() 或 reject()方法。所以我們在將 fulfullCallback和rejectCallback賦值給當前 promise 時,將其包裝一下。代碼如下:
promise: { then: function(_fulfillCallback, _rejectCallback) { let newPromiseAsyncFunc = promiseAsyncFunc() let fulfillFunc = function(value) { newPromiseAsyncFunc.fulfill(_fulfillCallback(value)) } let rejectFunc = function(err) { newPromiseAsyncFunc.fulfill(_rejectCallback(err)) } if (status === PENDING) { fulfillCallback = fulfillFunc rejectCallback = rejectFunc } else { value.then(fulfillFunc, rejectFunc) } return newPromiseAsyncFunc.promise } }
如此,我們變得到了一個可以鏈式調用的 promise。讓我們來測試一下:
const PENDING = Symbol("pending") const FULFILLED = Symbol("fulfilled") const REJECTED = Symbol("rejected") function makeThenable(value, status) { if (value && typeof value.then === "function") { return value } if (status === FULFILLED) { return { then: function(fulfillCallback, rejectCallback) { return makeThenable(fulfillCallback(value), FULFILLED) } } } if (status === REJECTED) { return { then: function(fulfillCallback, rejectCallback) { return makeThenable(rejectCallback(value), FULFILLED) } } } } // Promise 形式的異步方法定義 var promiseAsyncFunc = function() { var status = PENDING var fulfillCallback var rejectCallback var value return { fulfill: function(_value) { if (status !== PENDING) return value = makeThenable(_value, FULFILLED) status = FULFILLED if (typeof fulfillCallback === "function") { value.then(fulfillCallback) } }, reject(error) { if (status !== PENDING) return value = makeThenable(error, REJECTED) status = REJECTED if (typeof rejectCallback === "function") { value.then(null, rejectCallback) } }, promise: { then: function(_fulfillCallback, _rejectCallback) { let newPromiseAsyncFunc = promiseAsyncFunc() let fulfillFunc = function(value) { newPromiseAsyncFunc.fulfill(_fulfillCallback(value)) } let rejectFunc = function(err) { newPromiseAsyncFunc.fulfill(_rejectCallback(err)) } if (status === PENDING) { fulfillCallback = fulfillFunc rejectCallback = rejectFunc } else { value.then(fulfillFunc, rejectFunc) } return newPromiseAsyncFunc.promise } } } } let ownPromise = function(asyncCall) { let defer = promiseAsyncFunc() asyncCall(defer.fulfill, defer.reject) return defer.promise } let testChainedPromise = ownPromise(function(fulfill, reject) { setTimeout(() => { var randomNumber = Math.random() if (randomNumber > 0.5) fulfill(randomNumber) else reject(randomNumber) }, 1000) }) .then( data => { console.log(data) return "return value in then1 fulfill" }, err => { console.log(err) return "return value in then1 reject" } ) .then( data => { console.log(data) return "return value in then2 fulfill" }, err => { console.log(err) return "return value in then2 reject" } ) .then( data => { console.log(data) }, err => { console.log(err) } ) /** console output: 0.9931984611850693 return value in then1 fulfill return value in then2 fulfill */第六版 Promise: Error handling
這里我們只對異步調用和fulfill 回調中拋出的 error 進行處理。
首先是異步調用部分,我們將其 try catch 起來,在發生異常時調用 reject 方法,并將異常作為參數傳入。
let ownPromise = function(asyncCall) { let defer = promiseAsyncFunc() try { asyncCall(defer.fulfill, defer.reject) } catch (e) { defer.reject(e) } return defer.promise }
然后是 fulfill 中可能出現的異常。我們對fulfillCallback(value)可能出現的異常進行捕獲,并將異常傳遞給rejectCallback。
function makeThenable(value, status) { if (value && typeof value.then === "function") { return value } if (status === FULFILLED) { return { then: function(fulfillCallback, rejectCallback) { try { let newValue = fulfillCallback(value) return makeThenable(newValue, FULFILLED) } catch (e) { return makeThenable(rejectCallback(e), FULFILLED) } } } } if (status === REJECTED) { return { then: function(fulfillCallback, rejectCallback) { return makeThenable(rejectCallback(value), FULFILLED) } } } }
最后讓我們對完整的代碼進行測試:
const PENDING = Symbol("pending") const FULFILLED = Symbol("fulfilled") const REJECTED = Symbol("rejected") function makeThenable(value, status) { if (value && typeof value.then === "function") { return value } if (status === FULFILLED) { return { then: function(fulfillCallback, rejectCallback) { try { let newValue = fulfillCallback(value) return makeThenable(newValue, FULFILLED) } catch (e) { return makeThenable(rejectCallback(e), FULFILLED) } } } } if (status === REJECTED) { return { then: function(fulfillCallback, rejectCallback) { return makeThenable(rejectCallback(value), FULFILLED) } } } } // Promise 形式的異步方法定義 var promiseAsyncFunc = function() { var status = PENDING var fulfillCallback var rejectCallback var value return { fulfill: function(_value) { if (status !== PENDING) return value = makeThenable(_value, FULFILLED) status = FULFILLED if (typeof fulfillCallback === "function") { value.then(fulfillCallback) } }, reject(error) { if (status !== PENDING) return value = makeThenable(error, REJECTED) if (typeof rejectCallback === "function") { value.then(null, rejectCallback) } status = REJECTED }, promise: { then: function(_fulfillCallback, _rejectCallback) { let newPromiseAsyncFunc = promiseAsyncFunc() let fulfillFunc = function(value) { newPromiseAsyncFunc.fulfill(_fulfillCallback(value)) } let rejectFunc = function(err) { newPromiseAsyncFunc.fulfill(_rejectCallback(err)) } if (status === PENDING) { fulfillCallback = fulfillFunc rejectCallback = rejectFunc } else { value.then(fulfillFunc, rejectFunc) } return newPromiseAsyncFunc.promise } } } } let ownPromise = function(asyncCall) { let defer = promiseAsyncFunc() try { asyncCall(defer.fulfill, defer.reject) } catch (e) { defer.reject(e) } return defer.promise } let testChainedPromise = ownPromise(function(fulfill, reject) { throw Error("here is an error in asyncCall") setTimeout(() => { var randomNumber = Math.random() if (randomNumber > 0.5) fulfill(randomNumber) else reject(randomNumber) }, 1000) }) .then( data => { console.log(data) return "return value in then1 fulfill" }, err => { console.log(err.message) return "return value in then1 reject" } ) .then( data => { console.log(data) throw Error("here is an error in fulfill1") return "return value in then2 fulfill" }, err => { console.log(err.message) return "return value in then2 reject" } ) .then( data => { console.log(data) }, err => { console.log(err.message) } ) // console out: Error: here is an error in asyncCall return value in then1 reject Error: here is an error in fulfill1 return value in then2 reject總結
以上就是我們對于 Promise 的一個簡單的實現,實現思路主要參考了 Q-A promise library for javascript。該實現的 Promise 功能較為簡陋,僅實現了部分 api/規范。有任何意見和建議歡迎在評論區交流 ;)
進一步閱讀 && 引用對于 Promise 使用以及error handle 的講解:
https://medium.com/javascript...
Promise 實現庫之一: Q 對于 Promise 實現的講解:
https://github.com/kriskowal/...想了解更多 前端 和 數據可視化 ?
這里是我的 前端、_D3.js_ 、 數據可視化 的 github 地址, 歡迎 star & fork
ssthouse-blog
如果覺得本文不錯的話, 不妨點擊下面的鏈接關注一下 : )github 主頁
知乎專欄
掘金
想直接聯系我 ?郵箱: ssthouse@163.com
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/99993.html
摘要:前端日報精選裝飾器場景實戰配置之后端渲染理解同步異步和事件循環編寫高性能注意點線性漸變實現虛線等簡單實用圖形中文服務端渲染開發指南個人文章系列之事件類型個人文章使用必記掘金簡介掘金性能大亂斗前端雜談中簡單的數據圖形化 2017-10-27 前端日報 精選 JS 裝飾器(Decorator)場景實戰webpack配置之后端渲染JavaScript:理解同步、異步和事件循環編寫高性能js注...
摘要:作為開發同學的小伙伴客戶端的瀏覽器,有點小調皮還做了一個同源策略的限制,當我們的數據請求遇到不同源的情況下跨域,我們就得嘗試其它的通信方法,不能一條道走到黑。 showImg(https://segmentfault.com/img/bVburZO?w=600&h=450); Web2.0以來,Ajax的出世,解決了傳統表單提交頁面跳轉,閃爍白屏等問題。使得Web頁面可以實現局部更新,...
摘要:前端雜談權重權重想必大家都聽說過一些簡單的規則大部分人也都知道較長的權重會大于較短的權重高于但是具體規范是什么瀏覽器是按照什么標準來判定不同選擇器的權重的呢讓我們來看一下官方文檔是怎么說的第一個關鍵詞官方文檔中用特異性來表示一個和其元素的相 前端雜談: CSS 權重 (Specificity) css 權重想必大家都聽說過, 一些簡單的規則大部分人也都知道: 較長的 css sele...
閱讀 3565·2021-09-24 09:48
閱讀 1087·2021-09-10 10:51
閱讀 3268·2019-08-30 13:03
閱讀 3315·2019-08-30 12:51
閱讀 1388·2019-08-30 11:22
閱讀 1052·2019-08-29 18:38
閱讀 2035·2019-08-29 16:41
閱讀 3183·2019-08-29 15:32