摘要:工作當(dāng)中經(jīng)常會(huì)用到,在此進(jìn)行深入學(xué)習(xí)異步編程解決方案是異步編程的一種解決方案,比傳統(tǒng)的解決方案回調(diào)函數(shù)和事件更合理和更強(qiáng)大。所有源碼注釋見(jiàn)學(xué)習(xí)筆記
工作當(dāng)中經(jīng)常會(huì)用到Promise,在此進(jìn)行深入學(xué)習(xí)
異步編程解決方案Promise 是異步編程的一種解決方案,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更合理和更強(qiáng)大。它由社區(qū)最早提出和實(shí)現(xiàn),ES6 將其寫(xiě)進(jìn)了語(yǔ)言標(biāo)準(zhǔn),統(tǒng)一了用法,原生提供了Promise對(duì)象.
關(guān)于其他異步方案,有許多精彩的文章,在此不再詳述。接下來(lái)直接進(jìn)入正題。
項(xiàng)目地址This is a polyfill of the ES6 Promise
項(xiàng)目結(jié)構(gòu)
let p = new Promise(function (resolve, reject) { console.log(1); resolve(2); }) p.then(function (val) { console.log(val); })
這是一段簡(jiǎn)單實(shí)例,讓我們跟著源碼一起看發(fā)生了些什么,代碼當(dāng)中加入了個(gè)人理解的注釋。
首先看一下整理后的promise.js
class Promise { constructor (resolver) { this[PROMISE_ID] = nextId(); //生成id this._result = this._state = undefined; this._subscribers = [];//訂閱者 //一般使用時(shí),new時(shí)立即執(zhí)行一次使用者傳入的resolver,印證了一旦promise開(kāi)始執(zhí)行無(wú)法暫停 if (noop !== resolver) { typeof resolver !== "function" && needsResolver(); this instanceof Promise ? initializePromise(this, resolver) : needsNew();//調(diào)用resolver } } catch (onRejection) { return this.then(null, onRejection); } // finally 相當(dāng)于對(duì)當(dāng)前promise注冊(cè)resolve和reject兩種監(jiān)聽(tīng) //如果為 resolve 執(zhí)行一次cb 然后把原來(lái)的value繼續(xù)傳遞 finally (callback) { let promise = this; let constructor = promise.constructor; return promise.then(value => constructor.resolve(callback()).then(() => value), reason => constructor.resolve(callback()).then(() => { throw reason; })); } } Promise.prototype.then = then; export default Promise; Promise.all = all; Promise.race = race; Promise.resolve = Resolve; Promise.reject = Reject;
細(xì)節(jié)均在注釋當(dāng)中。
這里主要是定義了Promise類(lèi)和定義了一些方法如then,all等等,那么new Promise()主要是初始化了對(duì)象的一些屬性,同時(shí)會(huì)立即執(zhí)行resolver,印證了一旦promise開(kāi)始執(zhí)行無(wú)法暫停。接下來(lái)繼續(xù)我們的思路,看initializePromise方法發(fā)生了什么。
//initializePromise(this, resolver) function initializePromise (promise, resolver) { try { //執(zhí)行resolver 傳入回調(diào) resolver(function resolvePromise (value) { resolve(promise, value); }, function rejectPromise (reason) { reject(promise, reason); }); } catch (e) { reject(promise, e); } }
這里執(zhí)行了我們傳入的function,同時(shí)也給了使用者resolve和reject函數(shù),由于reject相對(duì)簡(jiǎn)單,這里我們先看reject如何實(shí)現(xiàn)。
// 通用的reject方法 function reject (promise, reason) { if (promise._state !== PENDING) { return; } promise._state = REJECTED; promise._result = reason; asap(publishRejection, promise);//as soon as possible }
reject方法中將promise的對(duì)象的狀態(tài)設(shè)置為rejected,設(shè)置了執(zhí)行的最終結(jié)果值result,隨后再asap(調(diào)度執(zhí)行策略)中執(zhí)行publishRejection,通知通過(guò)then方法注冊(cè)到_subscribers的訂閱者們,我被reject啦??!,來(lái)執(zhí)行回調(diào),下面是publishRejection方法。
function publishRejection (promise) { if (promise._onerror) { promise._onerror(promise._result); } publish(promise); } //通用的publish function publish (promise) { let subscribers = promise._subscribers; let settled = promise._state; //沒(méi)有訂閱者 if (subscribers.length === 0) { return; } let child, callback, detail = promise._result; //這里i+=3 是因?yàn)閠hen注冊(cè)時(shí) i是promise,i+1是resolve,i+2是reject for (let i = 0; i < subscribers.length; i += 3) { child = subscribers[i]; callback = subscribers[i + settled]; if (child) { invokeCallback(settled, child, callback, detail);//執(zhí)行回調(diào) } else { callback(detail); } } //通知完畢,清除訂閱 promise._subscribers.length = 0; }
上述就是reject之后的大致流程細(xì)節(jié)可以看注釋。
讓我們?cè)倩氐竭@里:
//initializePromise(this, resolver) function initializePromise (promise, resolver) { try { //執(zhí)行resolver 傳入回調(diào) resolver(function resolvePromise (value) { resolve(promise, value); }, function rejectPromise (reason) { reject(promise, reason); }); } catch (e) { reject(promise, e); } }
resolve發(fā)生了什么呢?
// 通用的resolve方法 繼續(xù)傳遞執(zhí)行 function resolve (promise, value) { if (promise === value) {//如果resolve原對(duì)象 reject(promise, selfFulfillment());//設(shè)置rejected狀態(tài) } else if (objectOrFunction(value)) {//如果val 是對(duì)象或函數(shù) handleMaybeThenable(promise, value, getThen(value));//getThen(value) 獲取val.then方法 } else {//not obj or not fnc fulfill(promise, value);//設(shè)置pending result val } }
resolve一個(gè)值,這里分了三種情況處理
1、如果resolve原對(duì)象,直接reject,拋錯(cuò)。
2、如果是對(duì)象或函數(shù),繼續(xù)處理。
3、如果是簡(jiǎn)單值,//改變promise 狀態(tài)為FULFILLED(完成狀態(tài)) 同時(shí)設(shè)置result,發(fā)起publish
如果非要簡(jiǎn)單理解,resolve就是不斷抽絲剝繭的處理直到給promise一個(gè)確定的完成態(tài)或拒絕態(tài)
fulfill方法比較簡(jiǎn)單,asap下文介紹
//改變promise 狀態(tài)為FULFILLED(完成狀態(tài)) 同時(shí)設(shè)置result function fulfill (promise, value) { if (promise._state !== PENDING) { return; } promise._result = value; promise._state = FULFILLED; if (promise._subscribers.length !== 0) {//通知 asap(publish, promise); } }
這里重點(diǎn)看handleMaybeThenable方法
/*maybeThenable:value;then:value.then*/ function handleMaybeThenable (promise, maybeThenable, then) {//thenable obj or promise /* originalThen : 定義的原始then;originalResolve:原始resolve*/ if (maybeThenable.constructor === promise.constructor && //判斷是否是promise且沒(méi)經(jīng)修改原生方法 then === originalThen && maybeThenable.constructor.resolve === originalResolve) { handleOwnThenable(promise, maybeThenable);//maybeThenable是一個(gè)原生的promise } else {//maybeThenable if (then === TRY_CATCH_ERROR) {// getThen 拋錯(cuò) reject(promise, TRY_CATCH_ERROR.error); TRY_CATCH_ERROR.error = null;//釋放引用 } else if (then === undefined) {//若不是一個(gè)thenable,直接完成態(tài) fulfill(promise, maybeThenable); } else if (isFunction(then)) {//若是一個(gè)thenable handleForeignThenable(promise, maybeThenable, then); } else {//若不是一個(gè)thenable,直接完成態(tài) fulfill(promise, maybeThenable);//改變promise 狀態(tài)為完成態(tài) 同時(shí)設(shè)置result } } }
這里就對(duì)傳入的值做了詳細(xì)區(qū)分
若是原生Promise對(duì)象:若是fulfilled或rejected,直接發(fā)起publish,如果是pending狀態(tài),調(diào)用then來(lái)注冊(cè)訂閱回調(diào)。
若是thenable:特殊處理handleForeignThenable
"其他fulfill或reject
這里看一下handleForeignThenable方法
/* * thenable 是函數(shù),有then方法 * */ //handleForeignThenable(promise, maybeThenable, then); function handleForeignThenable (promise, thenable, then) { asap(promise => {//asap這里默認(rèn)分析 setTimeout(fn,0) 下一輪任務(wù)開(kāi)始時(shí)執(zhí)行 var sealed = false;//是否有結(jié)果 //value:thenable傳入的參數(shù) ,嘗試執(zhí)行 var error = tryThen(then, thenable, value => {//fullfill時(shí), if (sealed) { return; } sealed = true; if (thenable !== value) {//如果不是直接resolve原對(duì)象 resolve(promise, value);//繼續(xù)對(duì)resolve的val進(jìn)行resolve處理 } else { fulfill(promise, value); } }, reason => {//reject if (sealed) { return; } sealed = true; reject(promise, reason); }, "Settle: " + (promise._label || " unknown promise")); if (!sealed && error) {//拋錯(cuò) 未正常執(zhí)行resolve reject sealed = true; reject(promise, error); } }, promise); }
看起來(lái)比較亂,但思路比較簡(jiǎn)單,這里就對(duì)thenable進(jìn)行嘗試執(zhí)行,如果返回結(jié)果正常就繼續(xù)resolve處理直到解析出一個(gè)值,否則拋錯(cuò)等等。
Then上文說(shuō)到了很多訂閱啦,publish啦,訂閱時(shí)哪里來(lái)的呢,上文只看到了每次執(zhí)行完?duì)顟B(tài)改變的時(shí)候要publish,publish給誰(shuí)呢,then方法會(huì)給出答案
export default function then (onFulfillment, onRejection) { const parent = this; //新建一個(gè)不執(zhí)行的promise對(duì)象用于返回結(jié)果,可鏈?zhǔn)秸{(diào)用 const child = new this.constructor(noop); if (child[PROMISE_ID] === undefined) {//TODO makePromise(child);//初始化基本的promie 屬性 } //promise state const {_state} = parent; if (_state) {// 如果狀態(tài)已完成或已拒絕,無(wú)需訂閱,直接執(zhí)行回調(diào)返回結(jié)果,印證了一旦promise有了結(jié)果無(wú)法再次改變 const callback = arguments[_state - 1]; asap(() => invokeCallback(_state, child, callback, parent._result)); } else {//訂閱來(lái)注冊(cè)回調(diào) subscribe(parent, child, onFulfillment, onRejection); } //then reuturn的新promise return child; }
這里情況分兩種
已完成或已拒絕:直接執(zhí)行回調(diào)返回結(jié)果,印證了一旦promise有了結(jié)果無(wú)法再次改變
pending未完成:訂閱來(lái)注冊(cè)回調(diào)
這里先看invokeCallback方法。
//asap(() => invokeCallback(_state, child, callback, parent._result)); //執(zhí)行回調(diào): function invokeCallback (settled, promise, callback, detail) { let hasCallback = isFunction(callback), value, error, succeeded, failed; if (hasCallback) { value = tryCatch(callback, detail);//嘗試執(zhí)行使用者then()傳入的回調(diào),成功時(shí)value 是then()注冊(cè)的回調(diào)方法的返回值 if (value === TRY_CATCH_ERROR) { failed = true; error = value.error; value.error = null; } else { succeeded = true; } if (promise === value) {//若return this reject(promise, cannotReturnOwn()); return; } } else {// then 未傳入相關(guān)回調(diào),繼續(xù)傳遞 value = detail; succeeded = true; } if (promise._state !== PENDING) { // noop } else if (hasCallback && succeeded) { resolve(promise, value);//value 可能為thenable,繼續(xù)處理,抽絲剝繭 } else if (failed) {//有cb 且失敗 reject(promise, error); } else if (settled === FULFILLED) {//無(wú)cb fulfill(promise, value); } else if (settled === REJECTED) {//無(wú)cb reject(promise, value); } }
接下來(lái)是重要的subscribe方法。
/* * parent:thenable * child : undefined or other * */ //subscribe(parent, child, onFulfillment, onRejection); //如果promise仍是pending,則將回調(diào)函數(shù)加入_subscribers等待通知 function subscribe (parent, child, onFulfillment, onRejection) { let {_subscribers} = parent;//取注冊(cè)的所有訂閱 let {length} = _subscribers; parent._onerror = null; _subscribers[length] = child;//擴(kuò)充訂閱 3個(gè)一循環(huán) _subscribers[length + FULFILLED] = onFulfillment; _subscribers[length + REJECTED] = onRejection; /* * 1、如果之前有訂閱且狀態(tài)是pending, 訂閱就好了,等待resolve完成時(shí)的發(fā)布通知執(zhí)行就好 * 2、如果之前有訂閱且狀態(tài)不是pending,繼續(xù)加入訂閱就好,length=0時(shí)已經(jīng)準(zhǔn)備調(diào)度發(fā)布了,pulish執(zhí)行時(shí)會(huì)清空 * 3、如果之前無(wú)訂閱且狀態(tài)是pending,訂閱就好了,等待resolve完成時(shí)的發(fā)布通知執(zhí)行就好 * 4、如下,趕緊調(diào)度執(zhí)行獲取結(jié)果 * */ if (length === 0 && parent._state) {//如果之前沒(méi)有訂閱且thenable已不是pending, asap(publish, parent); } }
上面就是訂閱的過(guò)程,主要是利用的js單線程的特性,且需要和fuifill和reject執(zhí)行時(shí)發(fā)布publish一起理解.
下面是asap方法
//下一輪事件循環(huán)執(zhí)行 export var asap = function asap (callback, arg) { queue[len] = callback;//2個(gè)一組 queue[len + 1] = arg; len += 2; if (len === 2) { /* 如果隊(duì)列長(zhǎng)度是2 ,那意味著我們需要調(diào)度一次隊(duì)列flush, 如果隊(duì)列flush完成前有其他的回調(diào)進(jìn)入隊(duì)列,這些進(jìn)入的回調(diào)會(huì)在當(dāng)前已調(diào)度的flush執(zhí)行 * */ // If len is 2, that means that we need to schedule(調(diào)度) an async flush. // If additional callbacks are queued before the queue is flushed, they // will be processed by this flush that we are scheduling. if (customSchedulerFn) { customSchedulerFn(flush); } else {//一般默認(rèn) scheduleFlush(); } } } function flush () { for (let i = 0; i < len; i += 2) { let callback = queue[i]; let arg = queue[i + 1]; callback(arg); queue[i] = undefined; queue[i + 1] = undefined; } len = 0;//邏輯清空隊(duì)列 }
本文默認(rèn)分析采用的調(diào)度策略時(shí)setTimeout方法,asap里維護(hù)了一個(gè)執(zhí)行隊(duì)列queue。這里涉及到了一些
js異步編程機(jī)制,推薦閱讀從瀏覽器多進(jìn)程到JS單線程,JS運(yùn)行機(jī)制最全面的一次梳理
本文主要是跟著源碼的思路簡(jiǎn)單過(guò)了一遍代碼,加入了個(gè)人的理解。同時(shí)還有一些如Promise.all等等方法將在下篇一起分析。
閱讀代碼前,也學(xué)習(xí)了阮一峰老師關(guān)于Promise的文章,在此一并感謝。
所有源碼注釋見(jiàn)promise學(xué)習(xí)筆記
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/94256.html
摘要:源碼學(xué)習(xí)本篇為上一篇源碼學(xué)習(xí)的補(bǔ)充,主要是來(lái)介紹和方法。那個(gè)率先改變的實(shí)例的返回值,就傳遞給的回調(diào)函數(shù)。基本介紹可見(jiàn)阮一峰老師的書(shū)籍。的狀態(tài)由決定,分成兩種情況。只有的狀態(tài)都變成,的狀態(tài)才會(huì)變成,此時(shí)的返回值組成一個(gè)數(shù)組,傳遞給的回調(diào)函數(shù)。 Promise源碼學(xué)習(xí)(2) 本篇為上一篇源碼學(xué)習(xí)(1)的補(bǔ)充,主要是來(lái)介紹Promise.all()和Promise.race()方法。閑話(huà)少敘...
摘要:源碼閱讀階段緊接上一篇這次我們開(kāi)始我們最常用到的部分的源碼解析傳入?yún)?shù)為兩個(gè)函數(shù)和判斷調(diào)用者是否為對(duì)象跳轉(zhuǎn)到了一個(gè)叫做的函數(shù)里面新建一個(gè)對(duì)象傳入函數(shù)傳入給和一個(gè)新的對(duì)象返回新的對(duì)象在這里我們先看看在調(diào)用者不是對(duì)象時(shí)到底做了什么比想象的要簡(jiǎn)單 源碼閱讀階段 緊接上一篇,這次我們開(kāi)始Promise我們最常用到的then部分的源碼解析. then() //傳入?yún)?shù)為兩個(gè)函數(shù),onFulfil...
摘要:源碼閱讀階段先理解根本吧想快點(diǎn)理解的話(huà)可以直接跳到下個(gè)標(biāo)題這部分根據(jù)理解將持續(xù)修改空函數(shù)用于判斷傳入構(gòu)造器的函數(shù)是否為空函數(shù)如果為空函數(shù)構(gòu)造一個(gè)對(duì)象并初始化狀態(tài)為終值回調(diào)狀態(tài)和隊(duì)列記錄內(nèi)部最后的一次錯(cuò)誤空對(duì)象標(biāo)識(shí)表示發(fā)生了錯(cuò)誤暴露模塊接口為 源碼閱讀階段 先理解Promise根本吧,想快點(diǎn)理解的話(huà)可以直接跳到下個(gè)標(biāo)題.這部分根據(jù)理解將持續(xù)修改. Promise(fn) function...
摘要:第三篇腳手架依賴(lài)的核心庫(kù)的源碼解析。該篇是這個(gè)系列文章的第三篇主要是對(duì)的源碼進(jìn)行分析講解。的源碼十分簡(jiǎn)單但實(shí)現(xiàn)的功能卻是十分的強(qiáng)大。源碼概括源碼主要包含了兩部分公共方法和私有方法。 react作為當(dāng)前十分流行的前端框架,相信很多前端er都有蠢蠢欲動(dòng)的學(xué)習(xí)它的想法。工欲善其事,必先利其器。這篇文章就簡(jiǎn)單的給大家介紹一下如何我快速的搭建一個(gè)react前端開(kāi)發(fā)環(huán)境。主要針對(duì)于react小白,...
摘要:下一篇大概就是源碼方面的學(xué)習(xí)筆記了龜速學(xué)習(xí)中這一次我是去看了下規(guī)范照例傳送門(mén)圖靈社區(qū)規(guī)范首先吧個(gè)人總結(jié)下該用的詞解決結(jié)婚拒絕婉拒終值值傳家寶拒因好人卡等等異常車(chē)禍理下概念我們的的就像是一場(chǎng)姻緣對(duì)吧解決呢就是結(jié)婚成功啦傳家寶也如愿的傳給下一代 下一篇大概就是源碼方面的學(xué)習(xí)筆記了...龜速學(xué)習(xí)中... 這一次我是去看了下Promises/A+規(guī)范照例傳送門(mén):圖靈社區(qū)Promises/A+規(guī)...
閱讀 1269·2021-11-19 09:40
閱讀 3128·2021-11-02 14:47
閱讀 3101·2021-10-11 10:58
閱讀 3224·2019-08-30 15:54
閱讀 2678·2019-08-30 12:50
閱讀 1732·2019-08-29 16:54
閱讀 473·2019-08-29 15:38
閱讀 1243·2019-08-29 15:19