摘要:可以理解為,先把中回調(diào)函數(shù)注冊到某個數(shù)組里,等執(zhí)時候,再執(zhí)行這個數(shù)組中的回調(diào)函數(shù)。方法注冊回調(diào)函數(shù),也可以理解為發(fā)布訂閱模式??偨Y(jié)一下方法把中回調(diào)函數(shù)注冊給上一個對象中的數(shù)組中,并交由上一個對象處理。
前言
Promise作為一種異步處理的解決方案,以同步的寫作方式來處理異步代碼。本文只涉及Promise函數(shù)和then方法,對其他方法(如catch,finally,race等)暫不研究。首先,看下Promise的使用場景。
使用場景例1 普通使用
new Promise((resolve, reject) => { console.log(1); resolve(2); console.log(3); }).then(result => { console.log(result); }).then(data => { console.log(data); }); // => 1 // => 3 // => 2 // => undefined
構(gòu)造函數(shù)Promise接受一個函數(shù)參數(shù)exactor,這個函數(shù)里有兩個函數(shù)參數(shù)(resolve和reject),在實例化之后,立即執(zhí)行這個exactor,需要注意的是,exactor里面除了resolve和reject函數(shù)都是異步執(zhí)行的,其他都是同步執(zhí)行。
通過它的then方法【注冊】promise異步操作成功時執(zhí)行的回調(diào),意思就是resolve傳入的數(shù)據(jù)會傳遞給then中回調(diào)函數(shù)中的參數(shù)??梢岳斫鉃?,先把then中回調(diào)函數(shù)注冊到某個數(shù)組里,等執(zhí)resolve時候,再執(zhí)行這個數(shù)組中的回調(diào)函數(shù)。如果then中的回調(diào)函數(shù)沒有返回值,那么下個then中回調(diào)函數(shù)的參數(shù)為undefined。 then方法注冊回調(diào)函數(shù),也可以理解為【發(fā)布訂閱模式】。
例2 Promise與原生ajax結(jié)合使用
function getUrl(url) { return new Promise((resolve, reject) => { let xhr = new XMLHttpRequest() xhr.open("GET", url, true); xhr.onload = function () { if (/^2d{2}$/.test(this.status) || this.status === 304 ) { resolve(this.responseText, this) } else { let reason = { code: this.status, response: this.response }; reject(reason, this); } }; xhr.send(null); }); } getUrl("./a.text") .then(data => {console.log(data)});
例3 Promise與$.ajax()結(jié)合使用
var getData=function(url) { return new Promise((resolve, reject) => { $.ajax({ type:"get", url:url, success:function(data){ resolve(data); }, error:function(err){ reject(err); } }); }); } getData("./a.txt") .then(data => { console.log(data); });Promise源碼分析
首先看一下Promise函數(shù)的源碼
function Promise(excutor) { let that = this; // 緩存當(dāng)前promise實例對象 that.status = PENDING; // 初始狀態(tài) that.value = undefined; // fulfilled狀態(tài)時 返回的信息 that.reason = undefined; // rejected狀態(tài)時 拒絕的原因 that.onFulfilledCallbacks = []; // 存儲fulfilled狀態(tài)對應(yīng)的onFulfilled函數(shù) that.onRejectedCallbacks = []; // 存儲rejected狀態(tài)對應(yīng)的onRejected函數(shù) function resolve(value) { // value成功態(tài)時接收的終值 if(value instanceof Promise) { return value.then(resolve, reject); } // 為什么resolve 加setTimeout? // 2.2.4規(guī)范 onFulfilled 和 onRejected 只允許在 execution context 棧僅包含平臺代碼時運行. // 注1 這里的平臺代碼指的是引擎、環(huán)境以及 promise 的實施代碼。實踐中要確保 onFulfilled 和 onRejected 方法異步執(zhí)行,且應(yīng)該在 then 方法被調(diào)用的那一輪事件循環(huán)之后的新執(zhí)行棧中執(zhí)行。 setTimeout(() => { // 調(diào)用resolve 回調(diào)對應(yīng)onFulfilled函數(shù) if (that.status === PENDING) { // 只能由pedning狀態(tài) => fulfilled狀態(tài) (避免調(diào)用多次resolve reject) that.status = FULFILLED; that.value = value; that.onFulfilledCallbacks.forEach(cb => cb(that.value)); } }); } function reject(reason) { // reason失敗態(tài)時接收的拒因 setTimeout(() => { // 調(diào)用reject 回調(diào)對應(yīng)onRejected函數(shù) if (that.status === PENDING) { // 只能由pedning狀態(tài) => rejected狀態(tài) (避免調(diào)用多次resolve reject) that.status = REJECTED; that.reason = reason; that.onRejectedCallbacks.forEach(cb => cb(that.reason)); } }); } // 捕獲在excutor執(zhí)行器中拋出的異常 // new Promise((resolve, reject) => { // throw new Error("error in excutor") // }) try { excutor(resolve, reject); } catch (e) { reject(e); } }
根據(jù)上面代碼,Promise相當(dāng)于一個狀態(tài)機,一共有三種狀態(tài),分別是 pending(等待),fulfilled(成功),rejected(失敗)。
that.onFulfilledCallbacks這個數(shù)組就是存儲then方法中的回調(diào)函數(shù)。執(zhí)行reject函數(shù)為什么是異步的,就是因為里面有setTimeout這個函數(shù)。當(dāng)reject執(zhí)行的時候,里面的 pending狀態(tài)->fulfilled狀態(tài),改變之后無法再次改變狀態(tài)了。
然后執(zhí)行onFulfilledCallbacks里面通過then注冊的回調(diào)函數(shù)。因為resolve執(zhí)行的時候是異步的,所以還沒執(zhí)行resolve里面具體的代碼時候,已經(jīng)通過then方法,把then中回調(diào)函數(shù)給注冊到了
onFulfilledCallbacks中,所以才能夠執(zhí)行onFulfilledCallbacks里面的回調(diào)函數(shù)。
我們看下then又是如何注冊回調(diào)函數(shù)的
/** * [注冊fulfilled狀態(tài)/rejected狀態(tài)對應(yīng)的回調(diào)函數(shù)] * @param {function} onFulfilled fulfilled狀態(tài)時 執(zhí)行的函數(shù) * @param {function} onRejected rejected狀態(tài)時 執(zhí)行的函數(shù) * @return {function} newPromsie 返回一個新的promise對象 */ Promise.prototype.then = function(onFulfilled, onRejected) { const that = this; let newPromise; // 處理參數(shù)默認值 保證參數(shù)后續(xù)能夠繼續(xù)執(zhí)行 onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value => value; onRejected = typeof onRejected === "function" ? onRejected : reason => { throw reason; }; // then里面的FULFILLED/REJECTED狀態(tài)時 為什么要加setTimeout ? // 原因: // 其一 2.2.4規(guī)范 要確保 onFulfilled 和 onRejected 方法異步執(zhí)行(且應(yīng)該在 then 方法被調(diào)用的那一輪事件循環(huán)之后的新執(zhí)行棧中執(zhí)行) 所以要在resolve里加上setTimeout // 其二 2.2.6規(guī)范 對于一個promise,它的then方法可以調(diào)用多次.(當(dāng)在其他程序中多次調(diào)用同一個promise的then時 由于之前狀態(tài)已經(jīng)為FULFILLED/REJECTED狀態(tài),則會走的下面邏輯),所以要確保為FULFILLED/REJECTED狀態(tài)后 也要異步執(zhí)行onFulfilled/onRejected // 其二 2.2.6規(guī)范 也是resolve函數(shù)里加setTimeout的原因 // 總之都是 讓then方法異步執(zhí)行 也就是確保onFulfilled/onRejected異步執(zhí)行 // 如下面這種情景 多次調(diào)用p1.then // p1.then((value) => { // 此時p1.status 由pedding狀態(tài) => fulfilled狀態(tài) // console.log(value); // resolve // // console.log(p1.status); // fulfilled // p1.then(value => { // 再次p1.then 這時已經(jīng)為fulfilled狀態(tài) 走的是fulfilled狀態(tài)判斷里的邏輯 所以我們也要確保判斷里面onFuilled異步執(zhí)行 // console.log(value); // "resolve" // }); // console.log("當(dāng)前執(zhí)行棧中同步代碼"); // }) // console.log("全局執(zhí)行棧中同步代碼"); // if (that.status === FULFILLED) { // 成功態(tài) return newPromise = new Promise((resolve, reject) => { setTimeout(() => { try{ let x = onFulfilled(that.value); resolvePromise(newPromise, x, resolve, reject); // 新的promise resolve 上一個onFulfilled的返回值 } catch(e) { reject(e); // 捕獲前面onFulfilled中拋出的異常 then(onFulfilled, onRejected); } }); }) } if (that.status === REJECTED) { // 失敗態(tài) return newPromise = new Promise((resolve, reject) => { setTimeout(() => { try { let x = onRejected(that.reason); resolvePromise(newPromise, x, resolve, reject); } catch(e) { reject(e); } }); }); } if (that.status === PENDING) { // 等待態(tài) // 當(dāng)異步調(diào)用resolve/rejected時 將onFulfilled/onRejected收集暫存到集合中 return newPromise = new Promise((resolve, reject) => { that.onFulfilledCallbacks.push((value) => { try { // 回調(diào)函數(shù) let x = onFulfilled(value); // resolve,reject都是newPromise對象下的方法 // x為返回值 resolvePromise(newPromise, x, resolve, reject); } catch(e) { reject(e); } }); that.onRejectedCallbacks.push((reason) => { try { let x = onRejected(reason); resolvePromise(newPromise, x, resolve, reject); } catch(e) { reject(e); } }); }); } };
正如之前所說的,先執(zhí)行then方法注冊回調(diào)函數(shù),然后執(zhí)行resolve里面代碼(pending->fulfilled),此時執(zhí)行then時候that.status為pending。
在分析that.status之前,先看下判斷onFulfilled的作用
onFulfilled =typeof onFulfilled === "function" ? onFulfilled : value => value;
如果then中并沒有回調(diào)函數(shù)的話,自定義個返回參數(shù)的函數(shù)。相當(dāng)于下面這種
new Promise((resolve, reject) => { resolve("haha"); }) .then() .then(data => console.log(data)) // haha
即使第一個then沒有回調(diào)函數(shù),但是通過自定義的回調(diào)函數(shù),依然把最開始的數(shù)據(jù)傳遞到了最后。
回過頭我們看下then中that.pending 判斷語句,發(fā)現(xiàn)真的通過onRejectedCallbacks 數(shù)組注冊了回調(diào)函數(shù)。
總結(jié)下then的特點:
當(dāng)status為pending時候,把then中回調(diào)函數(shù)注冊到前一個Promise對象中的onFulfilledCallbacks
返回一個新的Promise實例對象
整個resolve過程如下所示:
在上圖第5步時候,then中的回調(diào)函數(shù)就可以使用resolve中傳入的數(shù)據(jù)了。那么又把回調(diào)函數(shù)的
返回值放到resolvePromise里面干嘛,這是為了 鏈式調(diào)用。
我們看下resolvePromise函數(shù)
/** * 對resolve 進行改造增強 針對resolve中不同值情況 進行處理 * @param {promise} promise2 promise1.then方法返回的新的promise對象 * @param {[type]} x promise1中onFulfilled的返回值 * @param {[type]} resolve promise2的resolve方法 * @param {[type]} reject promise2的reject方法 */ function resolvePromise(promise2, x, resolve, reject) { if (promise2 === x) { // 如果從onFulfilled中返回的x 就是promise2 就會導(dǎo)致循環(huán)引用報錯 return reject(new TypeError("循環(huán)引用")); } let called = false; // 避免多次調(diào)用 // 如果x是一個promise對象 (該判斷和下面 判斷是不是thenable對象重復(fù) 所以可有可無) if (x instanceof Promise) { // 獲得它的終值 繼續(xù)resolve if (x.status === PENDING) { // 如果為等待態(tài)需等待直至 x 被執(zhí)行或拒絕 并解析y值 x.then(y => { resolvePromise(promise2, y, resolve, reject); }, reason => { reject(reason); }); } else { // 如果 x 已經(jīng)處于執(zhí)行態(tài)/拒絕態(tài)(值已經(jīng)被解析為普通值),用相同的值執(zhí)行傳遞下去 promise x.then(resolve, reject); } // 如果 x 為對象或者函數(shù) } else if (x != null && ((typeof x === "object") || (typeof x === "function"))) { try { // 是否是thenable對象(具有then方法的對象/函數(shù)) let then = x.then; if (typeof then === "function") { then.call(x, y => { if(called) return; called = true; resolvePromise(promise2, y, resolve, reject); }, reason => { if(called) return; called = true; reject(reason); }) } else { // 說明是一個普通對象/函數(shù) resolve(x); } } catch(e) { if(called) return; called = true; reject(e); } } else { // 基本類型的值(string,number等) resolve(x); } }
總結(jié)下要處理的返回值的類型:
1. Promise2本身 (暫時沒想通) 2. Promise對象實例 3. 含有then方法的函數(shù)或者對象 4. 普通值(string,number等)
自己觀察上面的代碼,我們發(fā)現(xiàn)不管返回值是 Promise實例,還是基本類型的值,最終都要用Promise2的resolve(返回值)來處理,然后把Promise2的resolve(返回值)中的返回值傳遞給Promise2的then中的回調(diào)函數(shù),這樣下去就實現(xiàn)了鏈式操作。
如果還不懂看下面的代碼:
var obj=new Promise((resolve, reject) => { resolve({name:"李四"}); }); obj.then(data=>{ data.sex="男"; return new Promise((resolve, reject) => { resolve(data); }); }).then(data => { console.log(data); // {name: "李四", sex: "男"} });
總之,調(diào)用Promise2中的resolve方法,就會把數(shù)據(jù)傳遞給Promise2中的then中回調(diào)函數(shù)。
resolve中的值幾種情況:
1.普通值
2.promise對象
3.thenable對象/函數(shù)
如果resolve(promise對象),如何處理?
if(value instanceof Promise) { return value.then(resolve, reject); }
跟我們剛才處理的返回值是Promise實例對象一樣, 最終要把resolve里面數(shù)據(jù)轉(zhuǎn)為對象或者函數(shù)或者基本類型的值
注意:then中回調(diào)函數(shù)必須要有返回值
var obj=new Promise((resolve, reject) => { resolve({name:"張三"}); }); obj.then(data=>{ console.log(data) // {name:"李四"} // 必須要有返回值 }).then(data => { console.log(data); // undefined });
如果then中回調(diào)函數(shù)沒有返回值,那么下一個then中回調(diào)函數(shù)的值不存在。
總結(jié)一下:
then方法把then中回調(diào)函數(shù)注冊給上一個Promise對象中的onFulfilledCallbacks數(shù)組中,并交由上一個Promise對象處理。
then方法返回一個新的Promise實例對象
resolve(data)或者resolvePromise(promise2, data, resolve, reject) 中的data值
最終為基本類型或者函數(shù)或者對象中的某一種
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/99507.html
摘要:從源碼看概念與實現(xiàn)是異步編程中的重要概念,它較好地解決了異步任務(wù)中回調(diào)嵌套的問題。這些概念中有趣的地方在于,標識狀態(tài)的變量如都是形容詞,用于傳入數(shù)據(jù)的接口如與都是動詞,而用于傳入回調(diào)函數(shù)的接口如及則在語義上用于修飾動詞的副詞。 從源碼看 Promise 概念與實現(xiàn) Promise 是 JS 異步編程中的重要概念,它較好地解決了異步任務(wù)中回調(diào)嵌套的問題。在沒有引入新的語言機制的前提下,這...
摘要:源碼閱讀階段先理解根本吧想快點理解的話可以直接跳到下個標題這部分根據(jù)理解將持續(xù)修改空函數(shù)用于判斷傳入構(gòu)造器的函數(shù)是否為空函數(shù)如果為空函數(shù)構(gòu)造一個對象并初始化狀態(tài)為終值回調(diào)狀態(tài)和隊列記錄內(nèi)部最后的一次錯誤空對象標識表示發(fā)生了錯誤暴露模塊接口為 源碼閱讀階段 先理解Promise根本吧,想快點理解的話可以直接跳到下個標題.這部分根據(jù)理解將持續(xù)修改. Promise(fn) function...
摘要:工作當(dāng)中經(jīng)常會用到,在此進行深入學(xué)習(xí)異步編程解決方案是異步編程的一種解決方案,比傳統(tǒng)的解決方案回調(diào)函數(shù)和事件更合理和更強大。所有源碼注釋見學(xué)習(xí)筆記 工作當(dāng)中經(jīng)常會用到Promise,在此進行深入學(xué)習(xí) 異步編程解決方案 Promise 是異步編程的一種解決方案,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更合理和更強大。它由社區(qū)最早提出和實現(xiàn),ES6 將其寫進了語言標準,統(tǒng)一了用法,原生提供了...
摘要:從最開始的到封裝后的都在試圖解決異步編程過程中的問題。為了讓編程更美好,我們就需要引入來降低異步編程的復(fù)雜性。寫一個符合規(guī)范并可配合使用的寫一個符合規(guī)范并可配合使用的理解的工作原理采用回調(diào)函數(shù)來處理異步編程。 JavaScript怎么使用循環(huán)代替(異步)遞歸 問題描述 在開發(fā)過程中,遇到一個需求:在系統(tǒng)初始化時通過http獲取一個第三方服務(wù)器端的列表,第三方服務(wù)器提供了一個接口,可通過...
摘要:下一篇大概就是源碼方面的學(xué)習(xí)筆記了龜速學(xué)習(xí)中這一次我是去看了下規(guī)范照例傳送門圖靈社區(qū)規(guī)范首先吧個人總結(jié)下該用的詞解決結(jié)婚拒絕婉拒終值值傳家寶拒因好人卡等等異常車禍理下概念我們的的就像是一場姻緣對吧解決呢就是結(jié)婚成功啦傳家寶也如愿的傳給下一代 下一篇大概就是源碼方面的學(xué)習(xí)筆記了...龜速學(xué)習(xí)中... 這一次我是去看了下Promises/A+規(guī)范照例傳送門:圖靈社區(qū)Promises/A+規(guī)...
閱讀 4016·2023-04-26 02:13
閱讀 2262·2021-11-08 13:13
閱讀 2750·2021-10-11 10:59
閱讀 1747·2021-09-03 00:23
閱讀 1316·2019-08-30 15:53
閱讀 2296·2019-08-28 18:22
閱讀 3063·2019-08-26 10:45
閱讀 746·2019-08-23 17:58