摘要:使用是極好的,它是如此有用以至于我覺得應該好好研究一下,甚至是實現一個簡易的版本。構造函數檢查參數例如是不是函數啊初始化,創建對象執行因此構造函數里面傳入的是立即被執行的。
使用Promise是極好的,它是如此有用以至于我覺得應該好好研究一下Promise,甚至是實現一個簡易的版本。實現之前,我們先來看看Promise的用途:
使用Promise callback hellPromise的第一個用途是能夠很好地解決回調黑洞的問題,假設要實現一個用戶展示的任務,這個任務分為三步:
獲取用戶信息
獲取用戶圖像
彈窗提示
不使用Promise,我們的實現可能是這樣子:
getUserInfo(id, function (info) { getUserImage(info.img, function () { showTip(); }) })
這里只是三步,如果有更長串的任務時,我們就會陷入到回調黑洞之中,為了解決這個問題,我們就可以使用Promise來處理這一長串任務,使用Promise的版本是這樣子的:
// getUserInfo返回promise getUserInfo(id) .then(getUserImage) .then(showTip) .catch(function (e) { console.log(e); });
原來向右發展的代碼,開始向下發展,這樣也更適合編程習慣,如果要讓我們的代碼更加健壯,我們就需要在每一步來處理錯誤信息,使用promise這后,我們只需要在最后的catch中做善后處理。
并發假如我們要顯示某一個頁的10條記錄,但是我們只有一個通過id獲取記錄的接口,這樣我們就需要發送10個請求,并且所有請求都完成之后再將記錄全部添加到頁面之中,Promise在這個場景下使用是特別合適的。
代碼可能是這樣子:
// ids要獲取信息的所有記錄id // getRecordById獲取記錄的接口,返回promise Promise.all(ids.map(getRecordById)) .then(showRecords) .catch(function (e) { console.log(e); });
這就是Promise的一些簡單的用途,當然令人興奮的是Promise已經是ES6的標準,而且目前很多瀏覽器已經原生支持Promise了。對于那些無法使用Promise的瀏覽器,我們就只能自己去實現了,下面就來看看Promise的簡單實現吧。
實現 warm up先來盜用一張MDN的圖,先來熱熱身,看看Promise的狀態遷移:
Promise有三種狀態:
pending:初始狀態, 非 fulfilled 或 rejected
fulfilled: 成功的操作
rejected: 失敗的操作
我們可以看出新建的Promise是pending狀態,fulfill之后就會執行調用then的回調函數了,倘若reject了就會調用catch來進行異常處理了,并且無論是調用then還是catch都會返回新的promise,這就是為什么promise可以鏈式調用了。
接著,我們來研究一下規范是怎么描述
promise的。這里只抽取核心部分,邊界問題不考慮。
檢查參數:例如executor是不是函數啊
初始化:[[State]]=pending,[[FulfillReactions]]=[],[[RejectReactions]]=[]
創建resolve對象:{[[Resolve]]: resolve, [[Reject]]: reject}
執行executor:executor(resolve, reject)
因此構造函數里面傳入的excuter是立即被執行的。FulfillReactions存儲著promise執行成功時要做的操作,RejectReactions存儲著promise是要執行的操作。
function Promise(resolver) { this._id = counter++; this._state = PENDING; this._result = undefined; this._subscribers = []; var promise = this; if (noop !== resolver) { try { resolver(function (value) { resolve(promise, value); }, function (reason) { reject(promise, reason); }); } catch (e) { reject(promise, e); } } }FulfillPromise(promise, value)
檢查[[state]],必須為pending(不是pending的表示已經解析,不能重復解析)
賦值:[[Result]]=value,[[state]]=fulfilled
觸發[[FulfillReactions]]的操作
和FulfillPromise聯系最緊密的就是ResolvePromise了,這里我們給出的是ResolvePromise的實現,區別只是多了直接解析Promise。
function resolve(promise, value) { // 要resolve的為promise(then的callback返回的是promise) if (typeof value === "object" && promise.constructor === value.constructor) { handleOwnThenable(promise, value); } // 要resolve的是值 else { if (promise._state !== PENDING) { return; } promise._result = value; promise._state = FULFILLED; asap(publish, promise); } } function handleOwnThenable(promise, thenable) { // 如果返回的promise已經完成 // 直接用該promise的值resolve父promise if (thenable._state === FULFILLED) { resolve(promise, thenable._result); } else if (thenable._state === REJECTED) { reject(promise, thenable._result); } // 如果返回的promise未完成 // 要等該promise完成再resolve父promise else { subscribe(thenable, undefined, function(value) { resolve(promise, value); }, function(reason) { reject(promise, reason); }); } }RejectPromise(promise, reason)
檢查[[state]],必須為pending(不是pending的表示已經解析,不能重復解析)
賦值:[[Result]]=reason,[[state]]=rejected
觸發[[RejectReactions]]的操作
觸發[[FulfillReactions]]和觸發[[RejectReactions]]實際就是遍歷數組,執行所有的回調函數。
function reject(promise, reason) { if (promise._state !== PENDING) { return; } promise._state = REJECTED; promise._result = reason; asap(publish, promise); }Promise.prototype.then(onFullfilled, onRejected)
promise=this
新建resultCapability三元組,{[[Promise]], [[Resolve]], [[Reject]]}([[Promise]]新建的)
fulfillReaction={[[Capabilities]]: resultCapability, [[Handler]]: onFulfilled}
rejectReaction={[[Capabilities]]: resultCapability, [[Handler]]: onRejected}
如果[[state]]是pending:fulfillReaction加入[[FulfillReactions]],rejectReaction加入[[RejectReactions]]
如果[[state]]是fulfilled:fulfillReaction加入執行隊列
如果[[state]]是rejected:rejectReaction加入執行隊列
返回resultCapability.[[Promise]]
這里可以看出構造函數和then的關系是很緊密的,新建的promise如果是異步操作,那么狀態就是pending,調用then時會新建子promise,并且將回調操作加入父promise的[[FulfillReactions]]或[[RejectReactions]]的數組里,這實際就是發布訂閱模式。
他們是這樣的關系:
無論是new promise還是調用then或catch,都會得到一個新的promise,這些promise都會訂閱父級promise的完成事件,父級promise完成之后就會執行一系列的回調操作,也就是發布。
Promise.prototype.catch(onRejected)then的語法糖:then(null, onRejected)
下面就是Promise原型:
Promise.prototype = { constructor: Promise, then: function (onFulfillment, onRejection) { var parent = this; var state = parent._state; if (state === FULFILLED && !onFulfillment || state === REJECTED && !onRejection) { return this; } var child = new Promise(noop); var result = parent._result; if (state) { var callback = arguments[state - 1]; asap(function () { invokeCallback(state, child, callback, result); }); } else { subscribe(parent, child, onFulfillment, onRejection); } return child; }, "catch": function (onRejection) { return this.then(null, onRejection); } };Promise.resolve(value)
新建promise
調用ResolvePromise(promise, value)(未列出,會判斷一些情況然后調用FulfillPromise)
返回promise
Promise.resolve = function (arg) { var child = new Promise(noop); resolve(child, arg); return child; };Promise.reject(value)
新建promise
調用RejectPromise(promise, value)
返回promise
Promise.reject = function (reason) { var child = new Promise(noop); reject(child, reason); return child; };Promise.all(iterator)
到這里我們已經能夠實現基本的promise了,Promise.all和Promise.race就不繼續描述了,有興趣的可以繼續去讀規范,這里上圖來說明我對這兩個函數的理解:
調用promise.all會新建一個對象來存儲所有promise的處理狀態,保存執行的結果,當remain為0時,就可以resolve 新建的promise,這樣就可以繼續往后執行了。
Promise.all = function (promises) { var child = new Promise(noop); var record = { remain: promises.length, values: [] }; promises.forEach(function (promise, i) { if (promise._state === PENDING) { subscribe(promise, undefined, onFulfilled(i), onRejected); } else if (promise._state === REJECTED) { reject(child, promise._result); return false; } else { --record.remain; record.values[i] = promise._result; if (record.remain == 0) { resolve(child, values); } } }); return child; function onFulfilled(i) { return function (val) { --record.remain; record.values[i] = val; if (record.remian === 0) { resolve(child, record.values); } } } function onRejected(reason) { reject(child, reason); } };Promise.race(iterator)
promise.race與promise.all類似,不過只要有一個promise完成了,我們就可以resolve新建的promise了。
Promise.race = function (promises) { var child = new Promise(noop); promises.forEach(function (promise, i) { if (promise._state === PENDING) { subscribe(promise, undefined, onFulfilled, onRejected); } else if (promise._state === REJECTED) { reject(child, promise._result); return false; } else { resolve(child, promise._result); return false; } }); return child; function onFulfilled(val) { resolve(child, val); } function onRejected(reason) { reject(child, reason); } };
這就是promise的基本內容了,完整代碼請戳這里。
其他問題 promises 穿透如果傳入then里面的參數不是函數,就會被忽略,這就是promise穿透的原因,所以永遠往then里面傳遞函數。答案可以從then方法里面調用的一個關鍵函數invokeCallback中找到答案:
function invokeCallback(settled, promise, callback, detail) { var hasCallback = (typeof callback === "function"), value, error, succeeded, failed; if (hasCallback) { try { value = callback(detail); } catch (e) { value = { error: e }; } if (value && !!value.error) { failed = true; error = value.error; value = null; } else { succeeded = true; } } // then的參數不是函數 // 會被忽略,也就是promise穿透 else { value = detail; succeeded = true; } if (promise._state === PENDING) { if (hasCallback && succeeded || settled === FULFILLED) { resolve(promise, value); } else if (failed || settled === REJECTED) { reject(promise, error); } } }
例如如下例子,結果都是輸出foo:
Promise.resolve("foo").then(Promise.resolve("bar")).then(function (result) { console.log(result); }); Promise.resolve("foo").then(null).then(function (result) { console.log(result); });擁抱金字塔
promise能夠很好的解決金字塔問題,但是有時候我們也是需要適當使用金字塔的,例如我們要同時獲取兩個promise的結果,但是這兩個promise是有關聯的,也就是有順序的,該怎么辦?
也許解決方案會是這樣,定義一個全局變量,這樣在第二個then里面就可以使用兩個promise的結果了。
var user; getUserByName("nolan").then(function (result) { user = result; return getUserAccountById(user.id); }).then(function (userAccount) { // 好了, "user" 和 "userAccount" 都有了 });
但是這不是最好的方案,此時何不拋棄成見,擁抱金字塔:
getUserByName("nolan").then(function (user) { return getUserAccountById(user.id).then(function (userAccount) { // 好了, "user" 和 "userAccount" 都有了 }); });
promise是如此強大而且難以理解,但是抓住實質之后其實并沒有想象的那么復雜,這也是為什么我要寫下這篇文章。更過關于如何正確使用promise,請看第三篇參考文章,強力推薦。
參考實現promise
MDN-Promise
ES6-Promise規范
We have a problem with promises
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/85939.html
摘要:構造函數的實現我們在使用的時候其實是使用關鍵字創建了一個的實例,其實是一個類,即構造函數,下面來實現構造函數。 showImg(https://segmentfault.com/img/remote/1460000018998456); 閱讀原文 概述 Promise 是 js 異步編程的一種解決方案,避免了 回調地獄 給編程帶來的麻煩,在 ES6 中成為了標準,這篇文章重點不是敘...
摘要:本次的任務假如。。。。。引擎發生了重大故障,方法變成了,為了拯救世界,需要開發一個模塊來解決此問題。實現首先要知道是什么是對異步編程的一種抽象。數組中任何一個為的話,則整個調用會立即終止,并返回一個的新的對象。 本次的任務 假如。。。。。 JavaScript v8 引擎發生了重大故障,Promise.all 方法變成了 undefined ,為了拯救 JavaScript 世界,需要...
摘要:通過或者拿到方法回調函數的返回值,然后調用,將新增的的和傳入到中。打印結果實現方法接收一個包含多個的數組,當有一個為狀態時,整個大的為,并執行回調函數。 前言 Promise大家一定都不陌生了,JavaScript異步流程從最初的Callback,到Promise,到Generator,再到目前使用最多的Async/Await(如果對于這些不熟悉的可以參考我另一篇文章《JavaScri...
摘要:內部總體上分為兩種情況,一種是當前對象狀態已經變為或,此時則直接把響應的回調函數添加到異步隊列中,另一種情況是當前對象狀態還是,此時則把響應的回調函數依次添加到數組中。 今天,我帶著大家一步一步跟著規范實現一個自己的Promise,大家可以對照我的第二篇文章Promise介紹--規范篇或官方規范來一一學習。 Promise內部有三個固定的狀態,我們在文件中提前定義。 const PEN...
摘要:如果實現滿足所有要求,則實現可能允許。本條款允許使用特定于實現的方法來采用已知一致承諾的狀態。接下來根據規范進行手寫實現注釋偷懶就將對應的規范標注出來,其實基本上就是對著規范實現。 如果要手寫實現promise,那么先看看promise/A+規范,再來實現,將會事半功倍。那么我先翻譯一下Promise/A+規范中的內容。 術語 1.1 promise 是一個帶有符合此規范的the...
閱讀 738·2021-11-11 16:54
閱讀 3053·2021-09-26 09:55
閱讀 2004·2021-09-07 10:20
閱讀 1198·2019-08-30 10:58
閱讀 1040·2019-08-28 18:04
閱讀 698·2019-08-26 13:57
閱讀 3584·2019-08-26 13:45
閱讀 1150·2019-08-26 11:42